Battle Brothers
0 of 0

File information

Last updated

Original upload

Created by

Adam

Uploaded by

AdamMil01

Virus scan

Safe to use

About this mod

Implement mods without overwriting whole files, to improve mod compatibility. Declare dependencies and other relationships between mods.

Requirements
Permissions and credits
Changelogs
To mod authors: Please use the new ::mods_queue function to allow your mods to participate in dynamic ordering. Also, if you compile your own .cnut files, you must exercise care with the command-line arguments. See the documentation for ::mods_hookClass.

This mod implements a few hooks that can be used to implement many types of mods without overwriting script files. It may not be complete, but can be expanded with additional capabilities if and when they're needed. The mod currently exposes the following functionality, as functions that you can call:

INSTALLATION

Just drop the .zip file into your game's data directory. No need to unzip it. NOTE: If you just want to use mods that depend on mod_hooks, you can stop reading here. The rest of this page is only for people who want to develop new mods.

EXAMPLES

For example mods implemented using these hooks, see any of my mods for Battle Brothers.

FUNCTIONS:

::mods_addHook(name, func = null)
Registers a hook that will be called by ::mods_callHook(name). Multiple functions can be registered for the same hook, in which case they'll be called in order. Arguments:
name - The name of the hook, which is arbitrary.
func - The function that will be called when the hook is invoked, or null to simply create the hook without registering a function. The function may take any number of parameters. If the hook function returns a non-null value, that value will be returned from ::mods_callHook and further hook processing will be halted.

::mods_callHook(name, ...)
Invokes hook functions by name. If no hooks have been registered, nothing will happen. If a hook function returns a non-null value, that value will be returned and further hook processing will be halted. If all hook functions return null, the function returns null. Arguments:
name - The name of the hook, which should match that provided to ::mods_addHook.
All additional arguments will be passed to the hook function.

::mods_removeHook(name, func)
Removes a hook that was previously registered. Arguments:
name - The name of the hook, which should match that provided to ::mods_addHook.
func - The function to remove. This must be the exact same function object that was registered.

::mods_hookNewObject(name, func, once = false)
A convenience method for hooking into the instantiation of a new object. Arguments:
name - The name of the object. If the object is implemented in scripts/x/y/z.cnut, the name should be "x/y/z"
func - A function that takes one argument, which is the newly instantiated object. Normally the function should return null, but if it returns a different value that value will be substituted for the allocated object.
once - If true, the hook will only be called the first time the object is instantiated (per campaign load). This is useful when hooking global objects so you can avoid degrading performance by leaving the hook around. The default is false.

::mods_hookNewObjectOnce(name, func)
A convenience method that calls ::mods_hookNewObject(name, func, true).

::mods_hookClass(name, func, abstract = false, childrenOnly = true)
A convenience method for hooking into the creation and derivation of classes. The method is a bit unusual, since it hooks when the class is instantiated and when child classes are derived (but not when child classes are instantiated). It does not hook when grandchild classes are derived, but the cascading mechanism probably takes care of that. Arguments:
name - The name of the class. If the class is implemented in scripts/x/y/z.cnut, the name should be "x/y/z".
func - A function that takes one argument, which is either the newly instantiated class instance or the newly derived subclass. You should use ::mods_override to modify the object.
abstract - If true, it's assumed that the class is abstract and no hook is registered for when the class in instantiated. Thus, func is only called for subclasses. The default is false.
childrenOnly - If false, all descendants of the class will be hooked. If true, children of the class will be hooked but grandchildren, etc. will not. The default is true. Note that hooking descendant classes is, in general, not possible, and this feature is implemented via a hack that examines the debug metadata of the compiled squirrel scripts. If you add or overwrite game classes using compiled .cnut files, it is important that you compile them in such a way that the script path embedded in the compiled .cnut file is of the form "scripts/.../foo.nut". In practice, this means compiling them from the root of the game's data directory, e.g. by running "sq -o scripts/items/foo.cnut -c scripts/items/foo.nut" and not by running "sq -o foo.cnut -c foo.nut".

::mods_hookBaseClass(name, func)
A convenience method that calls ::mods_hookClass(name, func, true).

::mods_hookChildren(name, func)
A convenience method that calls ::mods_hookBaseClass(name, func).

::mods_hookDescendants(name, func)
A convenience method that calls ::mods_hookClass(name, func, true, false).

::mods_isClass(obj, name)
Determines whether an object is a given Battle Brothers class, or derived from it. If so, returns the class or base class. If not, returns null. Arguments:
obj - A table representing a Battle Brothers object
name - A short class name, such as "player" (not the full name "scripts/entities/tactical/player")

::mods_addField(obj, className, fieldName, value)
Sets a field of an object or class within the 'm' dictionary commonly used by the game. The field need not exist. Arguments:
obj - The object or class whose field should be set
className - The short name of the class where the field should be set. If specified, the field will be set on the given class (or base class) if the object is or derives from the given class. If null, the property will not be set on any particular class.
fieldName - The name of the field to set
value - The value that the field should be set to

::mods_addMember(obj, className, memberName, value)
Sets a property of an object or class. The property need not exist. Arguments:
obj - The object or class whose property should be set
className - The short name of the class where the property should be set. If specified, the property will be set on the given class (or base class) if the object is or derives from the given class. If null, the property will not be set on any particular class.
memberName - The name of the property to set
value - The value that the property should be set to

::mods_getClassForOverride(obj, className)
Attempts to locate a class in a class's inheritance hierarchy. If found, the given class will be returned. Otherwise, obj will be returned. Arguments:
obj - The object or class whose fields or properties will be overridden.
className - The short name of the class whose members you'd like to override. The given class (or base class) will be returned if the object is or derives from it.

::mods_getField(obj, fieldName)
Gets a field of an object or class from the 'm' dictionary commonly used by the game. The field must exist. Arguments:
obj - The object or class whose property should be retrieved
fieldName - The name of the field to retrieve

::mods_getMember(obj, memberName)
Gets a property of an object or class. The property must exist. Arguments:
obj - The object or class whose property should be retrieved
memberName - The name of the property to retrieve

::mods_override(obj, key, value)
Sets a property of an object or class. The property must already exist. If the object is a class, the method is capable of following the chain of inheritance and setting the property on a base class if needed. Arguments:
obj - The object or class whose property should be set
key - The name of the property to set
value - The value that the property should be set to

::mods_overrideField(obj, fieldName, value)
Sets a field of an object or class in the 'm' dictionary commonly used by the game. The field must already exist. If the object is a class, the method is capable of following the chain of inheritance and setting the property on a base class if needed. Arguments:
obj - The object or class whose field should be set
fieldName - The name of the field to set
value - The value that the field should be set to

::mods_queue(codeName, expr, func)
This function lets you queue the loading of your mod and situate it relative to other mods (assuming those other mods also use ::mods_queue). For example, you can say that your mod depends on some other mod to ensure your mod gets loaded after that other mod. You can also say that your mod is incompatible with some other mod, or that your mod must be loaded before some other mod. You must call ::mods_registerMod to register your mod before using this function.
codeName - The code name of your mod that you passed to ::mods_registerMod, or null to use the code name of the most recently registered mod, presumably yours
expr - An expression describing the relationship of your mod to other mods. This is a string containing a comma-separated list of subexpressions. Each subexpression starts with an optional !, <, or > character and is followed by the code name of another mod and optionally followed by a version specification in parentheses. The names of the other mods are their code names that they should have passed to ::mods_registerMod. Examples are given below. If you have no particular relationships that you want to declare, but simply want to participate in the dynamic ordering system, you can pass null or an empty string. It is not necessary to declare a dependency on mod_hooks.
func - A function that is called (with zero arguments) at the appropriate time. For example, you might call ::mods_hookNewObject, ::mods_hookClass, or ::mods_registerJS in there. You can also pass null if you don't want a function to be called. This might be useful to simply assert that a dependency is installed.

Example expressions:
X - I require mod X and must be loaded after it.
>X - I don't require mod X but if it exists I must be loaded after it.
<X - I must be loaded before mod X. You should not use this to declare that X depends on your mod. Instead, let mod X make that declaration.
!X - I am incompatible with mod X.
X(3) - I require mod X version 3.
X(=3) - I require mod X version 3.
X(!=3) - I require mod X but not version 3.
X(>=3) - I require mod X version 3 or later.
X(<3) - I require mod X version 2 or earlier.
!X(>3) - I am incompatible with mod X version 4 or later.
!X(<=3) - I am incompatible with mod X version 3 or earlier.
!X(!=3) - I am incompatible with all versions of mod X except version 3.
X, >Y, !Z(2.6) - I require mod X, must be loaded after mod Y, and am incompatible with mod Z version 2.6.

::mods_registerCSS(path)
Registers a CSS hook, which will be processed when the main menu is loaded for the first time. Arguments:
path - The path to a CSS file, relative to ui/mods/. For example, if "mod_foo.css" is passed, the ui/mods/mod_foo.css file will be linked into the UI.

::mods_registerJS(path)
Registers a Javascript hook, which will be executed when the main menu is loaded for the first time. Arguments:
path - The path to a Javascript file, relative to ui/mods/. For example, if "mod_foo.js" is passed, the ui/mods/mod_foo.js file will be executed.

::mods_registerMod(codeName, version, friendlyName = null, extra = null)
Registers a mod. Calling this method is not required but will allow other mods to know about and potentially respond to the existence of your mod. Arguments:
codeName - The code name of your mod. This should be short and unique and should never change. It is the stable, unique identifier that other mods can use to detect your mod. It must be composed only of numbers, letters, and underscores. If you have multiple variants of your mod, you should probably use the same code name for all of them and only change the friendly name.
version - A nonnegative number specifying the version of your mod. You may use either integers or floats. Do not pass a string.
friendlyName - An optional, friendly name for your mod meant to be displayed to users. If omitted, codeName will be used.
extra - An optional table containing additional properties you want to associated with your mod registration

::mods_getRegisteredMod(codeName)
Gets a registered mod by name, or returns null if the mod is not registered. See ::mods_getRegisteredMods below for the properties of the mod object. Arguments:
codeName - The code name of the mod passed to ::mods_registerMod.

::mods_getRegisteredMods()
Gets a list of all registered mods. Each mod will be described by table with these properties:
Name - The mod's code name, as passed to ::mods_registerMod
Version - The mod's version
FriendlyName - The mod's friendly name
... and any additional properties specified in the extra parameter to ::mods_registerMod.

::rng_new(seed = 0)
Creates a new 'rng' class. (See below.) The seed must be an integer. If zero, the seed will be generated based on the current time.

VARIABLES

::rng
A random number generator, which is the default instance of the 'rng' class. (See below.)

CLASSES

rng
A default RNG is available from the ::rng variable, or you can create a new one with the ::rng_new function. Methods:
nextInt() - Generates an integer from 0 to 2147483647 (inclusive).
nextFloat() - Generates a float from 0 (inclusive) to 1 (exclusive).
next(a, b = null) - If given one argument, generates a number from 1 to a (inclusive). If given two arguments, generates a number from a to b (inclusive).

WHEN TO USE EACH TYPE OF HOOK

It's hard to give exact rules for when to use hookBaseClass, hookDescendants, hookNewObject, or hookClass (which is a combination of the others). It generally depends on how the game uses the script, and whether you're trying to hook a single class or a whole group of related classes (e.g. all "actors"). But for general rules:
  • If you want to hook a single, specific object, e.g. the heavy crossbow or the broken arm injury or the assassin background, then use hookNewObject.
  • If you want to hook a group of objects, e.g. all weapons, and you know that the game never instantiates an instance of the base class (or if you don't care when it does), then use hookDescendants or hookBaseClass. For example, the game always instantiates specific types of weapons; it never directly instantiates the "items/weapon/weapon" script, which is just a base class for weapons. So you use hookDescendants. In some cases you may know that a class has only children and not grandchildren, etc. In that case, it can be slightly more efficient to use hookBaseClass.
  • If you want to hook a group of objects and the base class, then use hookClass. For example, the "entity/tactical/player" script is the type used for your bros on the field. But it's also a base class for guests, such as the firstborn son or the envoy. If you want to hook only your bros, you'd use hookNewObject on "entity/tactical/player", but if you want to hook your bros and also your guests, you'd use hookClass on "entity/tactical/player". And if you want to hook only the guests and not your bros, you'd use hookBaseClass or hookDescendants.

That said, you can often convert cases where you'd use hookNewObject or hookClass into hookBaseClass. This is an optimization that can provide a performance improvement, because a baseClass hook is only executed once when the game is loaded, while a newObject hook (or a class hook, if it's not abstract) is executed every time the object is instantiated. For example, you could convert a hookNewObject on the assassin background into a hookBaseClass on the character_background base class and add some code inside the hook that detects the assassin subclass using some detail that you happen to know is specific to the assassin background. I sometimes do that in my own code, but it is an advanced usage that requires some assumptions and knowledge about how the game works and makes your mod harder to understand and maintain. I think if you follow the general rules above you'll usually be okay.

BUILT-IN HOOKS

inherit
Called when a class is inheriting from a base class. This can be used to change class members. The hook should normally return null, but if it returns a different value that value will be substituted for the class. The hook takes three arguments:
baseScriptName - The name of the base class script, e.g. "scripts/items/weapons/weapos"
currentScriptName - The name of the current class script, e.g. "scripts/items/weapons/knife"
c - The new derived class

new
Called whenever a new object is allocated. This can be used to change object members. The hook should normally return null, but if it returns a different value that value will be substituted for the new object. The hook takes two arguments:
scriptName - The name of the object allocated, e.g. "scripts/items/weapons/knife"
obj - The newly allocated object.

beforeCampaignLoad
Called before a campaign is loaded. The campaign will not be initialized yet. No arguments.

onCampaignLoaded
Called after a campaign is loaded. The hook is passed the world_state object as an argument.

root_state.onInit
Called when the game is loaded. The game will not be fully initialized yet. The hook takes one argument:
rootState - A reference to the root_state object

CONFLICTS

This mod conflicts with others that change scripts/root_state.nut or ui/main.html.