The HEMTT Book

What is HEMTT?

HEMTT is an open source tool used to build your Arma 3 mod into PBOs for development and release. It is a replacement for tools like Addon Builder and pboProject.

HEMTT supports the majority of the features found in Bohemia's tools, but prioritizes support for the workflows of best practices and modern modding.

HEMTT is slightly opinionated, most configurations are supported, but some niche features are not and will not be supported. This is to keep the codebase small and maintainable, as well as promote best practices.

HEMTT works best when used in a Git based project, but the HEMTT executable should not be added to your version control.

It is also recommended to use VSCode for your Arma 3 modding, as it has great extensions that will support you in your modding journey.

Additionally the HEMTT VSCode extension is available.

Warning

The VSCode Plugin is in development, and available as an alpha

Using HEMTT in VSCode's integrated terminal is the recommended way to use HEMTT, the output can have helpful information that may be missed otherwise.

Installation

Windows

HEMTT can be installed using Winget.

winget install hemtt

To update HEMTT with winget use:

winget upgrade hemtt

Linux & MacOS

HEMTT can be installed using an installer script.

curl -sSf https://hemtt.dev/install.sh | sh

The script can be ran again to update HEMTT.

Manual Download

The latest HEMTT release can be downloaded from the GitHub releases page.

Builds are available for Windows, Linux, and MacOS.

Compile from Source

HEMTT can be compiled from source using Rust.

HEMTT usually requires the latest stable version of Rust, older versions may work but are not supported.

You can use the cargo install --path bin command to install HEMTT while in the root of the repository.

Arma 3 Tools

HEMTT will use your installation of Arm 3 Tools to binarize supported files (p3d, rtm, wrp).

Installation

Windows

Arma 3 Tools can be installed using Steam. After installation, run the tools at least once to ensure the registry keys are set.

Linux

HEMTT can use either Proton or Wine to run the tools. wine64 is highly recommended, as using Proton will be much slower and may cause windows to pop up while running the tools. HEMTT will always use wine64 if it is available.

Steam

Arma 3 Tools can be installed using Steam with Proton. You can also use SteamCMD to install the tools on servers.

~/.local/share/arma3tools

The tools can be installed manually into ~/.local/share/arma3tools by copying the files from a Windows installation. If the tools are installed with Steam and inside this directory, HEMTT will prefer to use ~/.local/share/armatools.

HEMTT_BI_TOOLS Environment Variable

If you have the tools installed in a different location, you can set the HEMTT_BI_TOOLS environment variable to the path of the tools. HEMTT will always use this path if it is set.

Configuration

Every HEMTT project requires a .hemtt/project.toml file. This file contains all the information HEMTT needs to build your mod.

Previous versions of HEMTT supported a hemtt.json or hemtt.toml file, but these are no longer supported.

Minimum Configuration

The minimum configuration only requires a name and prefix to be set.

.hemtt/project.toml

name = "Advanced Banana Environment"
prefix = "abe"

You can read more about these options on the Minimum Configuration page.

Version

HEMTT uses a custom version format based on standards in the Arma 3 community. You can read more about it on the Version page.

Project

You can additionally configure optional settings for your project.

Main Prefix

The mainprefix option allows you to set the root prefix for your project, used before the prefix option. This is currently only used by hemtt launch.

.hemtt/project.toml

mainprefix = "z"

It should match the $PBOPREFIX$ file in each addon folder.

addons/main/$PBOPREFIX$

z\abe\addons\main

Files

You can add a list of files to be copied to the build directory. This is useful for including files that are not part of addons, such as a README.md, LICENSE, logos, or extensions. To include a folder, you must use a glob pattern that matches all files in that folder.

.hemtt/project.toml

[files]
include = [
    "mod.cpp",        # These files are copied to the build directory by default
    "meta.cpp",       # They do not need to be added to your list
    "LICENSE",
    "logo_ca.paa",
    "logo_co.paa",
    "python_code/**", # Copy the folder "python_code" including all its files
]
exclude = [
    "*.psd",          # By default this list is empty
    "addons/main/README.md",
]

include

By default, those 5 files are included in the build directory if they exist in the root of your project. You do not need to add them to your list. Additional files or glob paths can be added to the list.

.hemtt/project.toml

[files]
include = [
    "mod.cpp",      # These files are copied to the build directory by default
    "meta.cpp",     # They do not need to be added to your list
    "LICENSE",
    "logo_ca.paa",
    "logo_co.paa",
]

exclude

By default, no files are excluded from PBOs. You can add files or glob paths to the list.

.hemtt/project.toml

[files]
exclude = [
    "*.psd",        # By default this list is empty
    "addons/main/README.md",
]

properties

You can add a list of properties to be added to every PBO.

.hemtt/project.toml

[properties]
author = "ABE Team"
url = "https://github.com/ABE-Mod/ABE"

Minimum Configuration

The minimum configuration only requires a name and prefix to be set.

.hemtt/project.toml

name = "Advanced Banana Environment"
prefix = "abe"

name

The name of your mod, currently unused.

prefix

The prefix of your mod.

It should be used in the $PBOPREFIX$ file in each addon folder in the following format.

z\{prefix}\addons\main

addons/main/$PBOPREFIX$

z\abe\addons\main

Version Configuration

HEMTT uses the project version as part of the signing authority. It is also included as an property in built PBOs.

No [version] configuration is required if you use macros in your script_version.hpp file.

.hemtt/project.toml

[version]
path = "addons/main/script_version.hpp" # Default

major = 1 # Overrides path when set
minor = 0
patch = 4
build = 3 # Optional

git_hash = 0 # Default: 8

Macros

By default, HEMTT will look for addons/main/script_version.hpp and use the version components defined there. No [version] configuration is required. A major, minor, and patch version are required, and a build version is optional.

/addons/main/script_version.hpp

#define MAJOR 1
#define MINOR 0
#define PATCH 4 // `#define PATCHLVL` can also be used
#define BUILD 3 // Optional

If your macros are in another file, you can set the path with the version.path key.

.hemtt/project.toml

[version]
path = "addons/common/script_version.hpp"

Defined in Configuration

If you do not want to use a version file, you can set the version components directly in the configuration. If a version is defined in the configuration, the macros will not be used, even if a path is set.

.hemtt/project.toml

[version]
major = 1
minor = 0
patch = 4
build = 3 # Optional

Git Hash

By default, HEMTT will include the first 8 characters of the current git hash in the version. Since the git hash is enabled by default, without configuration HEMTT will require a git repository with at least one commit to be present. The git hash can be disabled by setting version.git_hash = 0, or configured to a different length.

.hemtt/project.toml

[version]
git_hash = 0 # Disabled
git_hash = 4 # 4 characters

Lint Configuration

HEMTT runs lints against your config and SQF files to check for errors, common mistakes, and improvements. Some lints can be disabled, configured, or changed in severity.

Lints can be kept in the project.toml file under the lints section, or in a separate .hemtt/lints.toml file. When kept in lints.toml, the lints. prefix is not required.

See the Analysis section for Config and SQF lints.

Configuration

Note

Some lints are not able to be disabled or reduced in severity. These are usually critical lints that are required for your project to work correctly.

There are 3 ways to configure a lint:

Disabling

A lint can be completely disabled by setting it to false. Only lints that are not critical can be disabled.

[lints.sqf]
command_case = false

Severity

A lint can have its severity changed. The severity can be Error, Warning, or Help. Only lints that are not critical errors can have their severity changed.

[lints.sqf]
command_case = "Warning"

Options

Some lints provide configuration options. Check the documentation for the lint to see what options are available.

[lints.sqf.command_case]
severity = "Error"
options.ignore = [
    "AGLtoASL",
    "ASLtoAGL",
]

Addon Configuration

In addition to .hemtt/project.toml, HEMTT also supports an optional addon.toml in each addon folder.

/addons/banana/addon.toml

[binarize]
enabled = false # Default: true
exclude = [
    "data/*.p3d",
    "data/anim/chop.rtm",
]

[rapify]
enabled = false # Default: true
exclude = [
    "missions/**/description.ext",
]

[files]
exclude = [
    ".vscode/**/*", # Exclude all files in the .vscode folder
    "data/*.psd",
]

[properties]
iso = "14001"

binarize

HEMTT's binarization of addons can be disabled for the addon by setting binarize.enabled to false, or disabled for specific files by adding glob patterns to binarize.exclude.

/addons/banana/addon.toml

[binarize]
enabled = false # Default: true
exclude = [
    "data/*.p3d",
    "data/anim/chop.rtm",
]

rapify

HEMTT's preprocessing & rapifying of addon configs can be disabled for the addon by setting rapify.enabled to false, or disabled for specific files by adding glob patterns to rapify.exclude.

When it is required to disable preprocessing & rapifying of config.cpp, it is recommended to create a separate addon to house any optional config, with the minimum amount of code required to make it work. Disabling preprocessing & rapifying will allow you to ship invalid config, which could cause issues for your players. It will also cause slower load times when the config is valid.

/addons/banana/addon.toml

[rapify]
enabled = false # Default: true
exclude = [
    "missions/**/description.ext",
]

files

files.exclude is an array of glob patterns that will be excluded and not packed into the PBO. It is important to note that this matches against files, not folders. To exclude a folder, you must use a glob pattern that matches all files in that folder.

/addons/banana/addon.toml

[files]
exclude = [
    ".vscode/**/*", # Exclude all files in the .vscode folder
    "data/*.psd",
]

properties

Much like the properties key in .hemtt/project.toml, the properties key in addon.toml allows you to add custom properties to the PBO.

/addons/banana/addon.toml

[properties]
iso = "14001"

P Drive

By default, HEMTT does not require a P Drive (sometimes called a Work Drive), and many mods do not require one.

HEMTT supports a P Drive for mods that do require one, but only to access \a3\.

Includes

Whenever possible, an .\include\ folder should be used in place of a P Drive. Any files placed in the .\include\ folder can be used from models or scripts as if they were in a P Drive. No $PBOPREFIX$ is required, but the full path must be created in the .\include\ folder.

The most common use case is for CBA's script_macros_common.hpp, you can see an example of this in ACE's GitHub Repo.

Default Behaviour, Ignored

By default, HEMTT will allow references to a P Drive, but will not fail the build if it does not exist. Even if a P Drive exists, it will not be used by HEMTT unless explicitly required by the project.

Disallowing P Drive

If a P Drive is explicitly disallowed by the project, it can specify as such.

.hemtt/project.toml

[hemtt.build]
pdrive = "disallow"

[hemtt.check]
pdrive = "ignore"

When disallowed by the project, HEMTT will fail to build the project if any references to a P Drive are found.

Requiring P Drive

If a P Drive is required by the project, it must specify as such. If the flag is not set, HEMTT will not allow the P Drive to be used.

.hemtt/project.toml

[hemtt.build]
pdrive = "require"

[hemtt.check]
pdrive = "ignore"

When required by the project, HEMTT will fail to build the project if all required files can not be resolved. HEMTT will only enable use of P:\a3\.

HEMTT will look for a P Drive in the following order:

P Drive (Mounted)

HEMTT will use P:\ as expected when it exists.

P Drive (Unmounted)

HEMTT will use the path configured in Arma 3 Tools as the P Drive, even if it is customized from the default and unmounted.

Arma 3 Installation

If no P Drive, mounted or unmounted, is found, HEMTT will attempt to extract the required files from your Arma 3 Installation.

Custom Commands

Danger

You are reading about an advanced and niche feature. This is specific for projects using Intercept and injecting custom SQF commands. This is not a feature that is commonly used.

Custom commands are defined using yaml inside the .hemtt/commands directory. The file name should be the name of the file, but this is not enforced. The command name will be taken from the defined name in the yaml file.

For reference, you can find all of Arma 3's commands in the arma3-wiki repo on the dist branch, under the acemod organization.

name: bananize
description: Bananize the player, forcing them to only throw bananas.
syntax:
- call: !Unary player
  ret:
    - Boolean
    - The success of the bananization
  params:
  - name: unit
    description: The unit to bananize
    type: Object
argument_loc: Global
effect_loc: Global
examples:
- <sqf>bananize player</sqf>

Commands

Setup

Development

Release

Options

--just

The build and dev commands can be used to build a single addon. It can be used multiple times to build multiple addons.

hemtt build --just myAddon

Danger

It is advised to only use this on very large projects that take a long time to build. It is advised to only use this after running the command once without --just to ensure all addons are built. Anytime you run any git commands that can modify files, you should run without --just to ensure all addons are up to date. Before reporting any unexpected behavior, try running without --just first.

Global Options

-t, --threads

Number of threads to use, defaults to the number of CPUs.

hemtt ... -t 4

-v

Verbosity level, can be specified multiple times.

hemtt ... -v # Debug
hemtt ... -vv # Trace

When running inside a CI platform like GitHub Actions, the output will always be set to trace.

Note

The full log can also be found at .hemttout/latest.log after each build

hemtt check

Checks the project for errors

Usage: hemtt [OPTIONS]

Options:
  -t, --threads <THREADS>  Number of threads, defaults to # of CPUs
  -v...                    Verbosity level
  -h, --help               Print help (see more with '--help')

Description

Checks the project for errors

hemtt check is the quickest way to check your project for errors. All the same checks are run as hemtt dev, but it will not write files to disk, saving time and resources.

hemtt localization coverage

Generate a coverage report

Usage: hemtt [OPTIONS]

Options:
      --format <FORMAT>  Output format [default: ascii] [possible values: ascii, json, pretty-json,
                         markdown]
  -h, --help             Print help (see more with '--help')

Description

Generate a coverage report

HEMTT will display a table of the coverage of language localization in the project. Showing the percentage, total strings, and how many addons have gaps in their localization.

Arguments

--format <FORMAT>

Output format

Possible values:

  • ascii - an ascii table for the terminal
  • json - compact json, ideal for machines
  • pretty-json - pretty json, ideal for humans
  • markdown - a markdown table, ideal for documentation or GitHub

hemtt localization sort

Sorts the stringtables

Usage: hemtt [OPTIONS]

Options:
      --only-lang  Only sort the languages within keys
  -h, --help       Print help (see more with '--help')

Description

Sorts the stringtables

HEMTT will:

  1. Sort the Packages in alphabetical order.
  2. Sort the Containers in alphabetical order (if any).
  3. Sort the Keys in alphabetical order.
  4. Sort the Localized Strings in the order of this table

Arguments

--only-lang

Only sort the languages within keys

hemtt new

Create a new project

Usage: hemtt <name>

Arguments:
  <name>  The name of the new project

Options:
  -h, --help  Print help (see more with '--help')

Description

Create a new project

hemtt new is used to create a new mod. It will create a new folder with the name you provide, and create some starting files.

It will ask for:

  • The full name of your mod
  • The author of your mod
  • The prefix of your mod
  • The main prefix of your mod
  • A license for your mod

Arguments

<name>

The name of the new project

This will create a new folder with the name you provide in the current directory.
It should be a valid folder name, using only letters, numbers, and underscores.

Example: hemtt new my_mod

hemtt dev

Build the project for development

Usage: hemtt [OPTIONS]

Options:
  -b, --binarize             Use BI's binarize on supported files
  -o, --optional <OPTIONAL>  Include an optional addon folder
  -O, --all-optionals        Include all optional addon folders
      --no-rap               Do not rapify (cpp, rvmat)
      --just <JUST>          Only build the given addon
  -t, --threads <THREADS>    Number of threads, defaults to # of CPUs
  -v...                      Verbosity level
  -h, --help                 Print help (see more with '--help')

Description

Build the project for development

hemtt dev is designed to help your development workflows. It will build your mod into .hemttout/dev, with links back to the original addon folders. This allows you to use file-patching with optional mods for easy development.

Configuration

.hemtt/project.toml

[hemtt.dev]
exclude = ["addons/unused"]

exclude

A list of addons to exclude from the development build. Includes from excluded addons can be used, but they will not be built or linked.

Arguments

-b, --binarize

Use BI's binarize on supported files

By default, hemtt dev will not binarize any files, but rather pack them as-is. Binarization is often not needed for development.

-o, --optional <OPTIONAL>

Include an optional addon folder

This can be used multiple times to include multiple optional addons.

hemtt dev -o caramel -o chocolate

-O, --all-optionals

Include all optional addon folders

--no-rap

Do not rapify (cpp, rvmat)

They will be copied directly into the PBO, not .bin version is created.

hemtt launch

Test your project

Usage: hemtt [OPTIONS] [CONFIG]... [-- <PASSTHROUGH>...]

Arguments:
  [CONFIG]...       Launches with the specified configurations
  [PASSTHROUGH]...  Passthrough additional arguments to Arma 3

Options:
  -e, --executable <EXECUTABLE>  Executable to launch, defaults to `arma3_x64.exe`
  -i, --instances <INSTANCES>    Launches multiple instances of the game
  -Q, --quick                    Skips the build step, launching the last built version
  -F, --no-filepatching          Disables file patching
  -b, --binarize                 Use BI's binarize on supported files
  -o, --optional <OPTIONAL>      Include an optional addon folder
  -O, --all-optionals            Include all optional addon folders
      --no-rap                   Do not rapify (cpp, rvmat)
      --just <JUST>              Only build the given addon
  -t, --threads <THREADS>        Number of threads, defaults to # of CPUs
  -v...                          Verbosity level
  -h, --help                     Print help (see more with '--help')

Description

Test your project

hemtt launch is used to build and launch a dev version of your mod. It will run the hemtt dev command internally after a few checks, options are passed to the dev command.

You can chain multiple configurations together, and they will be overlayed from left to right. Any arrays will be concatenated, and any duplicate keys will be overridden. With the below configuration, hemtt launch default vn ace would launch with all three configurations. Note that default must be specified when specifying additional configurations, default is only implied when no configurations are specified.

Configuration

hemtt launch requires the mainprefix option to be set.

Launch configurations can be stored in either .hemtt/project.toml under hemtt.launch, or in a separate file under .hemtt/launch.toml. The latter is useful for keeping your main configuration file clean. When using launch.toml, the hemtt.launch key is not required.

.hemtt/project.toml

mainprefix = "z"

# Launched with `hemtt launch`
[hemtt.launch.default]
workshop = [
    "450814997", # CBA_A3's Workshop ID
]
presets = [
    "main", # .html presets from .hemtt/presets/
]
dlc = [
    "Western Sahara",
]
optionals = [
    "caramel",
]
mission = "test.VR" # Mission to launch directly into the editor with
parameters = [
    "-skipIntro",           # These parameters are passed to the Arma 3 executable
    "-noSplash",            # They do not need to be added to your list
    "-showScriptErrors",    # You can add additional parameters here
    "-debug",
    "-filePatching",
]
executable = "arma3" # Default: "arma3_x64"
file_patching = false # Default: true
binarize = true # Default: false
rapify = false # Default: true

# Launched with `hemtt launch vn`
[hemtt.launch.vn]
extends = "default"
dlc = [
    "S.O.G. Prairie Fire",
]

# Launched with `hemtt launch ace`
[hemtt.launch.ace]
extends = "default"
workshop = [
    "463939057", # ACE3's Workshop ID
]

.hemtt/launch.toml

[default]
workshop = [
    "450814997", # CBA_A3's Workshop ID
]

[vn]
extends = "default"
dlc = [
    "S.O.G. Prairie Fire",
]

extends

The name of another configuration to extend. This will merge all arrays with the base configuration, and override any duplicate keys.

workshop

A list of workshop IDs to launch with your mod. These are not subscribed to, and will need to be manually subscribed to in Steam.

presets

A list of .html presets to launch with your mod. Exported from the Arma 3 Launcher, and kept in .hemtt/presets/.

dlc

A list of DLCs to launch with your mod. The fullname or short-code can be used.

Currently supported DLCs:

Full NameShort Code
Contactcontact
Global Mobilizationgm
S.O.G. Prairie Firevn
CSLA Iron Curtaincsla
Western Saharaws
Spearhead 1944spe
Reaction Forcesrf

optionals

A list of optional addon folders to launch with your mod.

mission

The mission to launch directly into the editor with. This can be specified as either the name of a folder in .hemtt/missions/ (e.g., test.VR would launch .hemtt/missions/test.VR/mission.sqm) or the relative (to the project root) path to a mission.sqm file or a folder containing it.

parameters

A list of Startup Parameters to pass to the Arma 3 executable.

executable

The name of the Arma 3 executable to launch. This is usually arma3 or arma3_x64. Do not include the .exe extension, it will be added automatically on Windows. Only paths relative to the Arma 3 directory are supported.

file_patching

Whether to launch Arma 3 with -filePatching. Equivalent to --no-filepatching or -F.

binarize

Whether to use BI's binarize on supported files. Equivalent to --binarize.

rapify

Provides the ability to disable rapify for the launch command. Equivalent to --no-rap.

Arguments

<CONFIG>

Launches with the specified configurations

Configured in either:

  • .hemtt/project.toml under hemtt.launch
  • .hemtt/launch.toml

-e, --executable <EXECUTABLE>

Executable to launch, defaults to arma3_x64.exe

Overrides the executable option in the configuration file.

Can be either a relative path to the Arma 3 directory, or an absolute path.

-e arma3profiling_x64 # Relative to the Arma 3 directory
-e "C:\Program Files\Steam\steamapps\common\Arma 3\arma3_x64.exe" # Absolute path

<PASSTHROUGH>

Passthrough additional arguments to Arma 3

Any options after -- will be passed to the Arma 3 executable. This is useful for passing additional Startup Parameters.

hemtt launch -- -world=empty -window

-i, --instances <INSTANCES>

Launches multiple instances of the game

If unspecified, it will default to 1.

-Q, --quick

Skips the build step, launching the last built version

Will throw an error if no build has been made, or no symlink exists.

-F, --no-filepatching

Disables file patching

-b, --binarize

Use BI's binarize on supported files

By default, hemtt dev will not binarize any files, but rather pack them as-is. Binarization is often not needed for development.

-o, --optional <OPTIONAL>

Include an optional addon folder

This can be used multiple times to include multiple optional addons.

hemtt dev -o caramel -o chocolate

-O, --all-optionals

Include all optional addon folders

--no-rap

Do not rapify (cpp, rvmat)

They will be copied directly into the PBO, not .bin version is created.

hemtt build

Build the project for final testing

Usage: hemtt [OPTIONS]

Options:
      --no-bin             Do not binarize the project
      --no-rap             Do not rapify (cpp, rvmat)
      --just <JUST>        Only build the given addon
  -t, --threads <THREADS>  Number of threads, defaults to # of CPUs
  -v...                    Verbosity level
  -h, --help               Print help (see more with '--help')

Description

Build the project for final testing

hemtt build will build your mod into .hemttout/build. It will binarize all applicable files, and will not create folder links like hemtt dev.

It is intended to be used for testing your mod locally before release.

Configuration

.hemtt/project.toml

[hemtt.build]
optional_mod_folders = false # Default: true

optional_mod_folders

By default, hemtt build will create separate mods for each optional mod folder.

Arguments

--no-bin

Do not binarize the project

They will be copied directly into the PBO. config.cpp, *.rvmat, *.ext will still be rapified. This can be configured per addon in addon.toml.

--no-rap

Do not rapify (cpp, rvmat)

They will be copied directly into the PBO. This can be configured per addon in addon.toml.

hemtt release

Build the project for release

Usage: hemtt [OPTIONS]

Options:
      --no-bin             Do not binarize the project
      --no-rap             Do not rapify (cpp, rvmat)
      --no-sign            Do not sign the PBOs or create a `bikey`.
      --no-archive         Do not create a zip archive of the release
  -t, --threads <THREADS>  Number of threads, defaults to # of CPUs
  -v...                    Verbosity level
  -h, --help               Print help (see more with '--help')

Description

Build the project for release

hemtt release will build your mod into .hemttout/release. It will create bisign files for all addons, and a bikey for validation.

It is intended to be used for releasing your mod.

It will create two zip archives in the releases folder: - {name}-latest.zip - {name}-{version}.zip

Configuration

hemtt release is built the same way as hemtt build, and will use its configuration.

[hemtt.release]
sign = false # Default: true
archive = false # Default: true

sign

If sign is set to false, a bikey will not be created, and the PBOs will not be signed.

Danger

All public releases of your mods should be signed. This will be a requirement of many communities, and is an important security feature. Do not use this unless you know what you are doing.

archive

If archive is set to false, a zip archive will not be created. The output will be in .hemttout/release.

Arguments

--no-bin

Do not binarize the project

They will be copied directly into the PBO. config.cpp, *.rvmat, *.ext will still be rapified. This can be configured per addon in addon.toml.

--no-rap

Do not rapify (cpp, rvmat)

They will be copied directly into the PBO. This can be configured per addon in addon.toml.

--no-sign

Do not sign the PBOs or create a bikey.

Danger

All public releases of your mods should be signed. This will be a requirement of many communities, and is an important security feature. Do not use this unless you know what you are doing.

--no-archive

Do not create a zip archive of the release.

The output will be in .hemttout/release.

hemtt script

Run a Rhai script on the project

Usage: hemtt [OPTIONS] <name>

Arguments:
  <name>  The name of the script to run, without .rhai

Options:
  -t, --threads <THREADS>  Number of threads, defaults to # of CPUs
  -v...                    Verbosity level
  -h, --help               Print help (see more with '--help')

Description

Run a Rhai script on the project

hemtt script is used to run a Rhai script on the project This is useful for automating tasks in a platform agnostic way, or requiring external dependencies.

Learn more about Scripts.

Arguments

<name>

The name of the script to run, without .rhai

Scripts are kept in .hemtt/scripts/

Rhai

Rhai is a simple scripting language that is embedded in HEMTT for Hooks and Scripts. It has a syntax similar to Javascript, and uses types similar to Rust.

A few examples of Rhai are provided below, but this is not a complete reference. The full Language Reference can be found in the Rhai Book.

Variables

Variables are defined using let and const, variables are dynamically typed. Variables defined with const cannot be changed.

let x; // ()
let x = 1; // (i32)
let x = 1.1; // (f32)
const x = "hello"; // (string)

Shadowing

Variables are shadowed in the current scope when redefined.

let x = 1;
print(x); // 1

{
    let x = 2;
    print(x); // 2
}

print(x); // 1

Logic

If

if foo() {
    print("foo is true");
} else if x == 2 {
    print("x is 2");
} else {
    print("foo is false and x is not 2");
}

If statements can be used as expressions.

let x = if foo() {
    1
} else {
    2
};

Libraries

HEMTT adds some additional functionality to Rhai through libraries.

Logging

Rhai

Rhai has two built in functions for logging, print and debug.

print(string)

Prints a string to the console.

print("Hello World!");
 INFO [post_release/test.rhai] Hello World!

debug(any)

Prints a representation of the value to the console if the --debug flag is passed to HEMTT.

debug(HEMTT.version().to_string());
debug(HEMTT.project().version.major());
DEBUG [post_release/test.rhai] "1.13.6"
DEBUG [post_release/test.rhai] 1

HEMTT

HEMTT provides additional logging functions.

info(string)

Prints a string to the console. Same functionality as print.

info("Hello World!");
 INFO [post_release/test.rhai] Hello World!

warn(string)

Prints a string to the console with a warning prefix.

warn("Hello World!");
 WARN [post_release/test.rhai] Hello World!

error(string)

Prints a string to the console with an error prefix.

error("Hello World!");
ERROR [post_release/test.rhai] Hello World!

fatal(string)

Prints string to the console with an error prefix, HEMTT will mark the build as failed and exit.

fatal("Hello World!");
ERROR [post_release/test.rhai] Hello World!
error: Hook signaled failure: post_release/test.rhai

HEMTT

The HEMTT constant gives access to information and the ability to modify the build process.

version()

Returns the version of HEMTT.

HEMTT.version().to_string(); // "1.13.6"
HEMTT.version().major(); // 1
HEMTT.version().minor(); // 13
HEMTT.version().patch(); // 6
HEMTT.version().build(); // ""

project()

Returns the project information.

See more about the Project library.

HEMTT.project().version().to_string(); // "1.3.0.1052"

mode()

Returns the current mode of HEMTT, one of:

  • dev
  • launch
  • build
  • release
HEMTT.mode(); // "release"

is_dev()

Returns true if the current mode is dev.

HEMTT.is_dev(); // false

is_launch()

Returns true if the current mode is launch.

HEMTT.is_launch(); // false

is_build()

Returns true if the current mode is build.

HEMTT.is_build(); // false

is_release()

Returns true if the current mode is release.

HEMTT.is_release(); // true

Project

version()

Returns the project version.

.build() will return 0 if the build number is not set.

HEMTT.project().version().to_string(); // "1.3.0.1052"
HEMTT.project().version().to_string_short(); // "1.3.0"
HEMTT.project().version().major(); // 1
HEMTT.project().version().minor(); // 3
HEMTT.project().version().patch(); // 0
HEMTT.project().version().build(); // 1052

name()

Returns the project name.

HEMTT.project().name(); // "Advanced Banana Environment"

prefix()

Returns the project prefix.

HEMTT.project().prefix(); // "abe"

mainprefix()

Returns the project main prefix, empty if not set.

HEMTT.project().mainprefix(); // "z"

File System

HEMTT has two types of file systems, which one is used depends on the context.

Scripts always use the real file system, as they run outside of the build process.

Hooks use the virtual file system during the pre_build, post_build, and pre_release phases.

HEMTT_VFS - Virtual File System

pre_build, post_build, and pre_release phases have access to the virtual file system. This means that the files are not actually written to disk. Files can be created, deleted, read from, written to, and these changes will appear only in the build output.

This is useful for modifying files with find-and-replace, or adding files to the build output, without the need for cleaning up after the build.

When using the virtual file system, the HEMTT_VFS constant is available. It is used as the root path.

Warning

During the pre_release phase, only files outside of addons should be changed. PBOs are already built, and changing files inside of addons will have no effect.

.hemtt/project.toml

[version]
major = 1
minor = 0
patch = 3

.hemtt/hooks/pre_build/set_version.rhai

// Get the path to the script_version.hpp file
let version = HEMTT_VFS
        .join("addons")
        .join("main")
        .join("script_version.hpp");
// Create (or overwrite) the file
let out = version.create_file();
out.write("#define MAJOR " + HEMTT.project().version().major() + "\n");
out.write("#define MINOR " + HEMTT.project().version().minor() + "\n");
out.write("#define PATCH " + HEMTT.project().version().patch() + "\n");
if HEMTT.project().version().build() != "" {
    out.write("#define BUILD " + HEMTT.project().version().build() + "\n");
}
print("Set version to " + HEMTT.project().version().to_string());

HEMTT_RFS - Real File System

All phases and scripts have access to the real file system. This means that the files are actually written to disk.

Danger

Be careful when modifying files while using the real file system, as you can destructively modify the project files. It is recommended to use the virtual file system whenever possible, and commit the changes to the project files prior to testing hooks.

When using the real file system, two additional constants are available. HEMTT_RFS is the root of the project, and HEMTT_OUT is the root of the build output.

.hemtt/hooks/pre_release/set_version.rhai

// Read the current contents of the docs/version.txt
// file from the project source
let version = HEMTT_RFS.join("docs").join("version.txt").open_file().read();
// Replace the placeholder version with the actual version
version.replace("0.0.0", HEMTT.project().version().to_string());
// Write the new contents to the build output
// create_file will overwrite the file if it exists
HEMTT_OUT.join("docs").join("version.txt").create_file().write(version);

Functions

All the functions below are available on both the virtual and real file systems.

join(string)

Joins the path with the given string.

HEMTT_VFS.join("addons"); // Points to ./addons in the project folder
HEMTT_VFS.join("addons").join("main"); // Points to ./addons/main in the project folder

exists()

Returns true if the path exists.

HEMTT_VFS.join("addons").exists(); // true
HEMTT_VFS.join(".hemtt").join("project.toml").exists(); // true

is_dir()

Returns true if the path is a directory.

HEMTT_VFS.join("addons").is_dir(); // true
HEMTT_VFS.join(".hemtt").join("project.toml").is_dir(); // false

is_file()

Returns true if the path is a file.

HEMTT_VFS.join("addons").is_file(); // false
HEMTT_VFS.join(".hemtt").join("project.toml").is_file(); // true

parent()

Returns the parent directory of the path.
Will panic if the path is root while using the real file system.
Will return the root path while using the virtual file system, if already at the root.

HEMTT_VFS.join("addons").parent(); // Points to ./
HEMTT_VFS.join(".hemtt").join("project.toml").parent(); // Points to ./.hemtt

file_name()

Returns the file name of the path.

HEMTT_VFS.join("addons").file_name(); // addons
HEMTT_VFS.join(".hemtt").join("project.toml").file_name(); // project.toml

copy(path)

Copies the file or directory to the given path.

HEMTT_VFS.join("docs").copy(HEMTT_OUT.join("docs")); // Copies the docs folder to the build output

move(path)

Moves the file or directory to the given path.

HEMTT_VFS.join("docs").move(HEMTT_OUT.join("docs")); // Moves the docs folder to the build output

list(path)

Lists the contents of the directory. If the path is a file, returns an empty array.

HEMTT_VFS.join("docs").list(); // Returns an array of paths of files and directories in the docs folder

open_file()

Opens the file for reading.

HEMTT_VFS.join("docs").join("readme.md").open_file(); // Returns a File object

create_file()

Creates the file for writing. Overwrites the file if it exists.

HEMTT_VFS.join("docs").join("readme.md").create_file(); // Returns a File object

remove_file()

Removes the file.

HEMTT_VFS.join("docs").join("readme.md").remove_file(); // Removes the file

read()

Reads the contents of the file.

HEMTT_VFS.join("docs").join("readme.md").open_file().read(); // Returns a string containing the contents of the file

write(string)

Writes the string to the file. Can be called multiple times to append to the file.

HEMTT_VFS.join("docs").join("readme.md").create_file().write("Hello World!"); // Writes "Hello World!" to the file

create_dir()

Creates the directory.

HEMTT_VFS.join("docs").create_dir(); // Creates the docs folder

create_dir_all()

Creates the directory and all parent directories.

HEMTT_VFS.join("docs").join("images").create_dir_all(); // Creates the images folder and the docs folder if they don't exist

remove_dir()

Removes the directory.

HEMTT_VFS.join("docs").remove_dir(); // Removes the docs folder

remove_dir_all()

Removes the directory and all its contents.

HEMTT_VFS.join("docs").remove_dir_all(); // Removes the docs folder and all its contents

Time

date(string)

Returns the current date in the given format.

date("[year]-[month]-[day] [hour]:[minute]:[second]"); // {{ time_1 }}
date("[year repr:last_two][month][day]"); // {{ time_2 }}

You can find the full list of format specifiers here.

Hooks

HEMTT supports hooks at various points in the build process. The hooks are written using Rhai. Rhai has an extension for VSCode that provides syntax highlighting.

Some example Rhai scripts can be found on the Rhai Playground. Additional commands can be requested as a GitHub Discussion.

Hooks are stored in the .hemtt/hooks/{phase} folders. The {phase} is the name of the phase that the hook is run in. The hooks are run in alphabetical order.

Example

.hemtt
└── hooks
    ├── pre_build
    │   ├── 01_example.rhai
    │   └── 02_example.rhai
    └── post_build
        ├── 01_example.rhai
        └── 02_example.rhai

Phases

There are 4 phases of the build process that can be hooked into:

HookFile System
pre_buildVirtual
post_buildVirtual
pre_releaseReal
post_releaseReal

pre_build

The pre_build hook is run before any preprocessing, binarization, or packing PBOs. This is the place to modify files that will be packed into the PBOs.

post_build

The post_build hook is run after all preprocessing, binarization, and packing PBOs. It is run before any release tasks.

pre_release

The pre_release hook is run before any release tasks. It is only run during the hemtt release command.

post_release

The post_release hook is run after all release tasks, and archives have been created. It is only run during the hemtt release command.

Hook Examples

Renaming the release zip

We want to modify the release zip to a different name format, we need to use the real file system during the post_release phase. This means that the hook will only run during the hemtt release command.

.hemtt/hooks/post_release/rename_zip.rhai

let releases = HEMTT_RFS.join("releases");
let src = releases.join(HEMTT.project().prefix() + "-latest.zip"); // "prefix-latest.zip"
let dst = releases.join("@" + HEMTT.project().prefix() + ".zip"); // "@prefix.zip"
if src.is_file() { // support --no-archive
    print("Moving zip to " + dst);
    if !src.move(dst) {
        fatal("Failed to move " + src + " to " + dst);
    }
}

Setting the version in a file

We want to set the version of the project in the mod.cpp file included in our builds, we need to use the virtual file system during the pre_build phase. This means that the hook will run during the hemtt build and hemtt release commands.

Since we are using the virtual file system, the file on disk will not be modified.

.hemtt/hooks/pre_build/set_version.rhai

let modcpp = HEMTT_VFS.join("mod.cpp").open_file().read(); // Read the contents of mod.cpp
modcpp.replace("0.0.0", HEMTT.project().version().to_string_short()); // Replace the placeholder version with the actual version
HEMTT_VFS.join("mod.cpp").create_file().write(modcpp); // Write the new contents over the old contents

Scripts

Scripts can be called from the command line using hemtt script <script>.

The files are located in the .hemtt/scripts folder, and are written in Rhai.

They have access to all the same libraries as hooks, but only use the real file system, since they run outside of the build process.

Scripts are useful for automating tasks that are not part of the build process, such as creating a new addon, or updating the version number.

Calling from Hooks

Scripts can be called from other scripts or from hooks using HEMTT.script(<script>). The script will still only have access to the real file system.

The script can return a value that will be passed back to the hook.

.hemtt/scripts/value.rhai

1 + 1 * 2

.hemtt/hooks/post_release/print_value.rhai

let value = HEMTT.script("value");
if value != 3 {
    fatal("Value is not 3");
}
println(value)

Examples

Bumping the minor version

.hemtt/scripts/bump_minor.rhai

// Read the current contents of script_version.hpp
let script_version = HEMTT_RFS.join("addons")
    .join("main")
    .join("script_version.hpp")
    .open_file()
    .read();

// Replace the current version with the new version
let prefix = "#define MINOR ";
let current = HEMTT.project().version().minor();
let next = current + 1;

script_version.replace(prefix + current.to_string(), prefix + next.to_string());

// Write the modified contents to script_version.hpp
HEMTT_RFS.join("addons")
    .join("main")
    .join("script_version.hpp")
    .create_file()
    .write(script_version);

hemtt utils inspect

Provides information about supported files. Supported: pbo, bikey, bisign

Usage: hemtt utils inspect [OPTIONS] <file>

Arguments:
  <file>
        File to inspect

Options:
  -v...
        Verbosity level

  -h, --help
        Print help (see a summary with '-h')

This will inspect a file and provide information about it.

Currently supported files are:

  • .pbo
  • .bikey
  • .bisign

hemtt utils pbo inspect

Inspect a PBO

Usage: hemtt utils pbo inspect [OPTIONS] <pbo>

Arguments:
  <pbo>
        PBO to inspect

Options:
  -v...
        Verbosity level

  -h, --help
        Print help (see a summary with '-h')

Provides information about a PBO.

This is the same as hemtt utils inspect but will assume the file is a PBO.

In some cases the output might be cut off in the terminal. Adjust the terminal.integrated.scrollback setting in VS Code if necessary.

Example

Check abe_main.pbo located in the build folder

hemtt.exe utils pbo inspect .hemttout\build\addons\abe_main.pbo

hemtt utils pbo extract

Extract a file from a PBO

Usage: hemtt utils pbo extract [OPTIONS] <pbo> <file> [output]

Arguments:
  <pbo>     PBO file to extract from
  <file>    File to extract
  [output]  Where to save the extracted file

Options:
  -v...                    Verbosity level
  -h, --help               Print help

Extracts a file from a PBO.

If no output is specified, it will be written to stdout.

hemtt utils pbo unpack

Unpack a PBO

Usage: hemtt utils pbo unpack [OPTIONS] <pbo> <output>

Arguments:
  <pbo>     PBO file to unpack
  <output>  Directory to unpack to

Options:
  -v...                    Verbosity level
  -h, --help               Print help

Unpacks a PBO to a directory.

A $PBOPREFIX$ file will be created in the output directory containing the prefix of the PBO.

All other properties from the PBO will be saved into properties.txt

hemtt utils pbo inspect

Inspect a PAA

Usage: hemtt utils paa inspect [OPTIONS] <paa>

Arguments:
  <paa>
        PAA to inspect

Options:
  -v...
        Verbosity level

  -h, --help
        Print help (see a summary with '-h')

Provides information about a PAA.

This is the same as hemtt utils inspect but will assume the file is a PAA.

In some cases the output might be cut off in the terminal. Adjust the terminal.integrated.scrollback setting in VS Code if necessary.

hemtt utils pbo convert

Convert a PAA to another format

Usage: hemtt utils paa inspect [OPTIONS] <paa> <output>

Arguments:
  <paa>
        PAA to convert

  <output>
        Output file

Options:
  -v...
        Verbosity level

  -h, --help
        Print help (see a summary with '-h')

Converts a PAA to another image format.

hemtt utils sqf case

Danger

This command requires manual review. It can have lots of false positives so you are strongly encouraged to check each modified file to ensure it is correct.

Fix capitalization in SQF commands

Usage: hemtt utils sqf [OPTIONS] <path>

Arguments:
  <path>
          Path to the SQF file or a folder to recursively fix

Options:
    -t, --threads
        Number of threads, defaults to # of CPUs

    -h, --help
        Print help information (use `-h` for a summary)

This will recursively correct all capitalization mistakes in SQF commands.

Example

private _positionASL = GetPosasl Player;
// becomes
private _positionASL = getPosASL player;

False Positives

This command does not full parse your SQF files.

It will not change words in strings in comments, but it may change words that will break your program

// script_macros.hpp
#define FALSE 0
#define TRUE 1

// fnc_someFunction.sqf
if (getNumber (configFile >> "someClass" >= TRUE)) then {...};
// becomes
if (getNumber (configFile >> "someClass" >= true)) then {...};
private _value = player getVariable [QGVAR(showHud), false];
// becomes
private _value = player getVariable [QGVAR(showHUD), false];

hemtt utils config inspect

Inspect a Config

Usage: hemtt utils config inspect [OPTIONS] <config>

Arguments:
  <config>
        Config to inspect

Options:
  -v...
        Verbosity level

  -h, --help
        Print help (see a summary with '-h')

Provides information about a Config.

This is the same as hemtt utils inspect but will assume the file is a Config.

In some cases the output might be cut off in the terminal. Adjust the terminal.integrated.scrollback setting in VS Code if necessary.

hemtt utils verify

Check a .bisign file against a public key and PBO

Usage: hemtt utils verify [OPTIONS] <pbo> <bikey>

Arguments:
  <pbo>
          PBO to verify

  <bikey>
          BIKey to verify against

Options:

    -v...
        Verbosity level

    -h, --help
        Print help information (use `-h` for a summary)

This will verify a signed PBO against a public key. This is useful for verifying that a PBO is signed correctly.

It will check:

  • The authority matches
  • The PBO is correctly sorted
  • The hashes match
  • A prefix property is present

Analysis

HEMTT will analyze your project for some common issues.

Preprocessor
Config
SQF

Preprocessor

HEMTT will provide warnings for common issues in your config, in both the preprocessing and rapifying stages.

Warning Suppression

Currently, HEMTT only allows the suppression of certain preprocessor warnings. To suppress a warning, use the following structure:

#pragma hemtt suppress { warning code } { scope = line }

The warning code can be one of the following:

CodeDescription
pw3_padded_argPadded argument in a macro call

The scope can be one of the following, if not specified, the scope will be line.

ScopeDescription
lineSuppresses the warning for the next line
fileSuppresses the warning for the remainder of the current file, not including includes
configSuppresses the warning for the remainder of the current config, including includes

Preprocessor Flags

HEMTT provides a few preprocessor flags to control the behavior of the preprocessor.

FlagDescription
pw3_ignore_formatIgnores padded arguments in ARR_N, WARNING_N, TRACE_N, FORMAT_N, etc. macros
pe23_ignore_has_includeAssume any #if __has_include is false

The scope of these flags is the same as the warning suppression scope.

Preprocessor Warnings

[PW1] Redefine Macro

This warning is emitted when a macro is defined more than once.

#define FOO 1
#define FOO 2

It may also appear when a macro is defined in a file that is included more than once.

// foo.hpp
#define FOO 1

// bar.hpp
#include "foo.hpp"
#include "foo.hpp"

[PW2] Invalid Config Case

This warning is emitted when config.cpp is not all lowercase, e.g. Config.cpp.

[PW3] Padded Argument

This warning is emitted when an argument to a macro is padded with spaces.

#define Introduction(var1, var2) var1, meet var2
HELLO(Jim, Bob)

This would produce Jim, meet Bob instead of Jim, meet Bob. (Note the extra space before Bob).

By default, all macros are checked, but a flag can be set to ignore ARR_N, WARNING_N, TRACE_N, FORMAT_N, etc. macros.

#pragma hemtt flag pw3_ignore_format { scope = line }

Lints - Conifg


invalid_value

Code: L-C01
Default Severity: Error
Minimum Severity: Error

Reports on any values in the config that could not be parsed into a valid config value.

Example

Incorrect

class MyClass {
    data = 1.0.0; // invalid value, should be quoted
};

Correct

class MyClass {
    data = "1.0.0";
};

Explanation

Arma configs only support Strings, Numbers, and Arrays. While other tools would guess that 1.0.0 is a string (often called auto-quote), this behaviour can introduce unintentional mistakes and is not supported by HEMTT.


duplicate_property

Code: L-C02
Default Severity: Error
Minimum Severity: Error

Reports on duplicated properties.

Example

Incorrect

class MyClass {
    data = 1;
    data = 2;
};

Incorrect

class MyClass {
    data = 1;
    class data {
        value = 1;
    };
};

Explanation

Properties on a class must be unique, regardless of the type of property.


duplicate_classes

Code: L-C03
Default Severity: Error
Minimum Severity: Error

Reports on duplicated child classes.

Example

Incorrect

class MyClass {
    class data {
        value = 1;
    };
    class data {
        value = 2;
    };
};

Incorrect

class MyClass {
    class child;
    class child {
        value = 1;
    };
};

Explanation

Children classes can only be defined once in a class.


external_missing

Code: L-C04
Default Severity: Error
Minimum Severity: Error

Reports on classes that extend an external class that is not present in the config

Example

Incorrect

class MyClass: ExternalClass {
    value = 1;
};

Correct

class ExternalClass;
class MyClass: ExternalClass {
    value = 1;
};

Explanation

Classes that extend an external class must be declared in the config.

Read more about class inheritance.


external_parent_case

Code: L-C05
Default Severity: Warning
Minimum Severity: Help

Reports on uses of base classes with incorrect case compared to the parent definition

Example

Incorrect

class BaseClass {};
class MyClass: baseclass {};

Correct

class BaseClass {};
class MyClass: BaseClass {};

Explanation

While Arma does not care about the case of class names, HEMTT wants you to have pretty code.


unexpected_array

Code: L-C06
Default Severity: Error
Minimum Severity: Error

Reports on properties that are not expected to be arrays, but are defined as arrays

Example

Incorrect

class MyClass {
    data = {1, 2, 3};
};

Correct

class MyClass {
    data[] = {1, 2, 3};
};

Explanation

Arrays in Arma configs are denoted by [] after the property name.


expected_array

Code: L-C07
Default Severity: Error
Minimum Severity: Error

Reports on properties that are expected to be arrays, but are not defined as arrays

Example

Incorrect

class MyClass {
    data[] = 1;
};

Correct

class MyClass {
    data = 1;
};

Explanation

Only properties that are arrays must have [] after the property name.


missing_semicolon

Code: L-C08
Default Severity: Error
Minimum Severity: Error

Reports on properties that are missing a semicolon

Example

Incorrect

class MyClass {
    data = 1
};

Correct

class MyClass {
    data = 1;
};

Incorrect

class MyClass {
    data = 1;
}

Correct

class MyClass {
    data = 1;
};

Explanation

All properties must end with a semicolon, including classes.


magwell_missing_magazine

Code: L-C09
Default Severity: Error
Minimum Severity: Warning

Reports on magazines that are defined in CfgMagazineWells but not in CfgMagazines

Example

Incorrect

class CfgMagazineWells {
    class abe_banana_shooter {
        abe_main[] = {
            "abe_cavendish",
            "abe_plantain",
            "external_banana"
        };
    };
};
class CfgMagazines {
    class abe_cavendish {};
};

Correct

class CfgMagazineWells {
    class abe_banana_shooter {
        abe_main[] = {
            "abe_cavendish",
            "abe_plantain",
            "external_banana"
        };
    };
};
class CfgMagazines {
    class abe_cavendish {};
    class abe_plantain {};
};

Explanation

Magazines defined in CfgMagazineWells that are using the project's prefix (abe in this case) must be defined in CfgMagazines as well. This is to prevent accidental typos or forgotten magazines.


class_missing_braces

Code: L-C10
Default Severity: Error
Minimum Severity: Error

Reports on classes that use inheritance without braces

Example

Incorrect

class External;
class AlsoExternal: External;
class MyClass: AlsoExternal {
    data = 1;
};

Correct

class External;
class AlsoExternal: External {};
class MyClass: AlsoExternal {
    data = 1;
};

Explanation

All classes using inheritance with a parent class must use braces {}, even if the class has no properties.

Lints - SQF


required_version

Code: L-S01
Default Severity: Error
Minimum Severity: Error

Checks for command usage that requires a newer version than specified in CfgPatches

Example

Incorrect

class CfgPatches {
    class MyAddon {
        units[] = {};
        weapons[] = {};
        requiredVersion = 2.00;
    };
};
private _leaky = getWaterLeakiness vehicle player; // getWaterLeakiness requires 2.16

Check the wiki to see what in version commands were introduced.


event_insufficient_version

Code: L-S02IV
Default Severity: Error
Minimum Severity: Error

Checks for event handlers that require a newer version than specified in CfgPatches

Example

Incorrect

class CfgPatches {
    class MyAddon {
        units[] = {};
        weapons[] = {};
        requiredVersion = 2.00;
    };
};
_this addEventHandler ["OpticsModeChanged", { // Requires 2.10
    hint 'Optics mode changed';
}];

Check the wiki to see what in version events were introduced.


event_unknown

Code: L-S02UE
Default Severity: Warning
Minimum Severity: Warning

Checks for unknown event used in event handlers

Configuration

  • ignore: List of unknown event names to ignore
[lints.sqf.event_unknown]
options.ignore = [
    "HealingReceived",
]

Example

Incorrect

_this addEventHandler ["HealingReceived", { // HealingReceived is not a valid event
    hint 'Healing received';
}];

Check the wiki to see what events are available.


event_incorrect_command

Code: L-S02IC
Default Severity: Error
Minimum Severity: Error

Checks for event handlers used with incorrect commands

Example

Incorrect

_this addEventHandler ["MPHit", {
    hint 'Hit';
}];

Correct

_this addMPEventHandler ["MPHit", {
    hint 'Hit';
}];

static_typename

Code: L-S03
Default Severity: Help
Minimum Severity: Help

Checks for typeName on static values, which can be replaced with the string type directly

Example

Incorrect

if (typeName _myVar == typeName "") then {
    hint "String";
};

Correct

if (typeName _myVar == "STRING") then {
    hint "String";
};

Explanation

typeName is a command that returns the type of a variable. When used on a constant value, it is slower than using the type directly.


command_case

Code: L-S04
Default Severity: Help
Minimum Severity: Help

Checks command usage for casing that matches the wiki

Configuration

  • ignore: An array of commands to ignore
[lints.sqf.command_case]
options.ignore = [
    "ASLtoAGL",
    "AGLtoASL",
]

Example

Incorrect

private _leaky = getwaterleakiness vehicle player;

Correct

private _leaky = getWaterLeakiness vehicle player;

if_assign

Code: L-S05
Default Severity: Help
Minimum Severity: Help

Checks if statements that are used as assignments when select or parseNumber would be more appropriate

Example

Incorrect

private _x = if (_myVar) then {1} else {0};

Correct

private _x = parseNumber _myVar;

Incorrect

private _x = if (_myVar) then {"apple"} else {"orange"};

Correct

private _x = ["orange", "apple"] select _myVar;

Explanation

if statements that are used as assignments and only return a static value can be replaced with the faster select or parseNumber commands.


find_in_str

Code: L-S06
Default Severity: Help
Minimum Severity: Help

Checks for find commands that can be replaced with in

Example

Incorrect

if (_haystack find _needle > -1) ...

Correct

if (_needle in _haystack) ...

Explanation

The in command is faster than find when searching for a substring in a string.


select_parse_number

Code: L-S07
Default Severity: Help
Minimum Severity: Help

Checks for select commands that can be replaced with parseNumber

Example

Incorrect

private _isWater = [0, 1] select (surfaceIsWater getPos player);

Correct

private _isWater = parseNumber (surfaceIsWater getPos player);

Incorrect

private _isLand = [1, 0] select (surfaceIsWater getPos player);

Correct

private _isLand = parseNumber !(surfaceIsWater getPos player);

Explanation

Using select on an array with 0 and 1 can be replaced with parseNumber for better performance.


format_args

Code: L-S08
Default Severity: Error
Minimum Severity: Warning

Checks for format commands with incorrect argument counts

Example

Incorrect

private _text = format ["%1", "Hello", "World"];

Correct

private _text = format ["%1", "Hello World"];

Incorrect

private _text = format ["%1 %2", "Hello World"];

Correct

private _text = format ["%1 %2", "Hello", "World"];

Explanation

The format and formatText commands requires the correct number of arguments to match the format string.


banned_commands

Code: L-S09
Default Severity: Error
Minimum Severity: Warning

Checks for broken or banned commands.

Configuration

  • banned: Additional commands to check for
  • ignore: An array of commands to ignore
[lints.sqf.banned_commands]
options.banned = [
    "execVM",
]
options.ignore = [
    "echo",
]

Example

Incorrect

echo "Hello World"; // Doesn't exist in the retail game

Explanation

Checks for usage of broken or banned commands.


if_not_else

Code: L-S11
Default Severity: Help (Disabled)
Minimum Severity: Help

Checks for unneeded not

Example

Incorrect

if (!alive player) then { player } else { objNull };

Correct

if (alive player) then { objNull } else { player };

! can be removed and else order swapped


var_all_caps

Code: L-S17
Default Severity: Help
Minimum Severity: Help

Checks for global variables that are ALL_CAPS and may actually be a undefined macro

Configuration

  • ignore: An array of vars to ignore
[lints.sqf.var_all_caps]
options.ignore = [
    "XMOD_TEST", "MYMOD_*",
]

Example

Incorrect

private _z = _y + DO_NOT_EXIST;

Explanation

Variables that are all caps are usually reserved for macros. This should should help prevent any accidental typos or uses before definitions when using macros. .


in_vehicle_check

Code: L-S18
Default Severity: Help
Minimum Severity: Help

Recommends using isNull objectParent X instead of vehicle X == X

Example

Incorrect

if (vehicle player == player) then { ... };

Correct

if (isNull objectParent player) then { ... };

Explanation

Using isNull objectParent x is faster and more reliable than vehicle x == x for checking if a unit is currently in a vehicle.


bool_static_comparison

Code: L-S20
Default Severity: Help
Minimum Severity: Help

Checks for a variable being compared to true or false

Example

Incorrect

if (_x == true) then {};
if (_y == false) then {};

Correct

if (_x) then {};
if (!_y) then {};

invalid_comparisons

Code: L-S21
Default Severity: Help
Minimum Severity: Help

Checks for if statements with impossible or overlapping conditions

Example

Incorrect

// This will never be true
if (_x < 20 && _x > 30) then { ... };
// If _x is less than 20, it will also be less than 10
if (_x < 20 && _x < 10) then { ... };

Explanation

This lint checks for if statements with impossible or overlapping conditions. This can be caused by typos or incorrect logic. HEMTT is not able to determine the intent of the code, so it is up to the developer to fix the condition.


this_call

Code: L-S22
Default Severity: Help (Disabled)
Minimum Severity: Help

Checks for usage of _this call, where _this is not necessary

Example

Incorrect

_this call _my_function;

Correct

call _my_function;

Explanation

When using call, the called code will inherit _this from the calling scope. This means that _this is not necessary in the call, and can be omitted for better performance.


reasign_reserved_variable

Code: L-S23
Default Severity: Error
Minimum Severity: Error

Prevents reassigning reserved variables

Example

Incorrect

call {
    _this = 1;
};
{
    private _forEachIndex = random 5;
} forEach allUnits;

Explanation

Reassigning reserved variables can lead to unintentional behavior.


marker_update_spam

Code: L-S24
Default Severity: Help
Minimum Severity: Help

Checks for repeated calls to global marker updates

Example

Incorrect

"my_marker" setMarkerAlpha 0.5;
"my_marker" setMarkerDir 90;
"my_marker" setMarkerSize [100, 200];
"my_marker" setMarkerShape "RECTANGLE";

Correct

"my_marker" setMarkerAlphaLocal 0.5;
"my_marker" setMarkerDirLocal 90;
"my_marker" setMarkerSizeLocal [100, 200];
"my_marker" setMarkerShape "RECTANGLE";

Explanation

The setMarker* commands send the entire state of the marker to all clients. This can be very expensive if done repeatedly.
Using the setMarker*Local on all calls except the last one will reduce the amount of data sent over the network.