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.
Recommended Workflow
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.
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
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
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
- hemtt new - Create a new project
Development
- hemtt check - Check the project for errors
- hemtt dev - Build the project for local development
- hemtt launch - Launch Arma 3 with your mod and dependencies
- hemtt build - Build the project for local testing
Release
- hemtt release - Build the project for 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
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.
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:
- Sort the Packages in alphabetical order.
- Sort the Containers in alphabetical order (if any).
- Sort the Keys in alphabetical order.
- 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 Name | Short Code |
---|---|
Contact | contact |
Global Mobilization | gm |
S.O.G. Prairie Fire | vn |
CSLA Iron Curtain | csla |
Western Sahara | ws |
Spearhead 1944 | spe |
Reaction Forces | rf |
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
underhemtt.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.
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
.
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.
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.
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:
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
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
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:
Code | Description |
---|---|
pw3_padded_arg | Padded argument in a macro call |
The scope can be one of the following, if not specified, the scope will be line
.
Scope | Description |
---|---|
line | Suppresses the warning for the next line |
file | Suppresses the warning for the remainder of the current file, not including includes |
config | Suppresses 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.
Flag | Description |
---|---|
pw3_ignore_format | Ignores padded arguments in ARR_N , WARNING_N , TRACE_N , FORMAT_N , etc. macros |
pe23_ignore_has_include | Assume 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.