STEAM GROUP
DAHM4PD DAHM4PD
STEAM GROUP
DAHM4PD DAHM4PD
199
IN-GAME
915
ONLINE
Founded
September 8, 2015
Language
English
 This topic has been pinned, so it's probably important
DorentuZ` Aug 17, 2017 @ 1:37pm
[HOWTO] Basic module definitions for DAHM/DorHUD
Definition file
A module is a standalone mod within the framework. It can be enabled/disabled without removing the files. The most simplistic example is as follows:
local module = DMod:new("<identifier>") -- id must be unique -- some useful stuff here return module -- the return values are what's registered by the framework

This will create a mod that doesn't do anything.

Hooks
Hooking into files is the same as with any other hook. You create a file, define the functions you want to overwrite and you register the file to be loaded at a certain time. For example, a post require hook for the lib/entry path can be defined as follows:
module:hook_post_require("lib/entry", "entry", [priority], [prefer luac])

Note that it'll automatically add the .lua extension. If you want to use a luac file, set the third (or fourth) argument to true. It's also possible to provide a numeric priority argument to overwrite the module priority for this file only. These arguments can be swapped so you don't have to remember exactly what the order was.

The files are relative to the mod folder. If you want to use the root folder, you can start the path with an @ character. NOTE: Only supported from version 1.5.


Object hooks
In plain lua, you'd write an object hook by replacing the function. This still works, but it won't give you much control over the functions. But more importantly, sanity checks, administration and debugging function calls has to be done manually. The framework therefore provides object hooking support. For example, suppose you want to hook into the "on_downed" function in the player manager to play a sound:
-- get module instance local module = ... -- get local instance of the class and let the framework set some -- variables to make logging easier. this step is not strictly required. local PlayerDamage = module:hook_class("PlayerDamage") -- hook into the on_downed function. this function doesn't take extra arguments module:hook(PlayerDamage, "on_downed", function(self) module:call_orig(PlayerDamage, "on_downed", self) self._unit:sound():say("f11e_plu", false) end, false)
It's important that you don't mix hooks and locally stored function backups. There's a few variants on calling and retrieving the original function, but you probably won't need those.

The additional false parameter after the function is an optimization thing. It indicates whether the original function can return a value. If all hooks set it to false, then it doesn't bother with the return values, making the function a little faster. It may not be much, but it adds up when there's hundreds of hooks. If you're not sure about the value, then don't set it. It defaults to returning a value.


Pre- and Post-Hooks
Since version 1.7 there's explicit support for pre- and post-hooks. This doesn't really add anything new, it just makes it easier to write simple functions. So the previous example would now become
local module = ... local PlayerDamage = module:hook_class("PlayerDamage") module:pre_hook(PlayerDamage, "on_downed", function(self) self._unit:sound():say("f11e_plu", false) end, false)
Much cleaner. Note that when you have a choice between a pre- and a post-hook, you should always go for the pre-hook for performance reasons.


Keybinds
Keybinds are special hooks and really are two things in one: you define a keybind and can assign a button to that keybind at the same time. Let's take the hud_toggle_name_labels from the HUD module as an example (and assign a default binding for the purpose of this example):
module:hook("OnKeyPressed", "hud_toggle_name_labels", "f3", "GAME", callback_function)

This keybind will be executed when you press "f3" when playing the game (read: not in the menu). I really recommend against assigning default bindings; having a menu option with type keybind is a much better option so the players can set it themselves and (hopefully) know what's happening.

The arguments to the hook are dynamic and supported arguments are default binding (string|nil), game state = (GAME|MENU), key state = (PRESSED|RELEASED|ANY), function(key_pressed). You can leave any of the state arguments out.


Extra parameters
  • Module priority
  • Module dependencies
  • Information about the mod (i.e. name, author, version, description).
  • Automatic updates (both modworkshop and paydaymods support).
local module = DMod:new("<identifier>", { name = "My DAHM module", version = "1.0", author = "dorentuz", description = { english = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", spanish = "Nulla facilisis quis augue sollicitudin imperdiet.", } })

All texts have support for multiple languages. In the example above, the name is a fixed string, whereas the description will be in Spanish when the game is played in that language.


Automatic updates
It's probably safe to assume everyone has upgraded to version 1.4 of the mod. However, to make sure some older versions do not crash you can add a check around some functions. For example, modworkshop update support was only added in that version. To enable automatic updates for this platform, you can use this:
if Util:version_compare(DorHUD:version(), "1.3.99") > 0 then module:set_update({ id = "14267", -- id on the platform you're using platform = "modworkshop", -- optional, defaults to paydaymods archive_root_folder = "p/a/t/h/dorhud/mod", -- optional, when the useful bit in in a subfolder archive_file_filter = "only_this_file.lua", -- optional, when you only need a single file password = "secret" -- optional }) end

Of course it's much easier to add the thing as an additional update parameter in the definition. The bare minimum for automatic updates is the id. Unless specified otherwise, paydaymods.com is used. Only zip files are supported.


Dependencies
If a module depends on another module, you can do two things: you can make sure you provide a numeric priority that's higher than the module in question (mods with the highest priority will be loaded last, they have final say) or you can specify exactly what the mod depends on:
DMod:new("<identifier>", { dependencies = { "my_required_dependency", "my_other_required_dependency", "[my_optional_dependency]" } }

Options
When a module has options, it should normally provided them via the definition file:
module:add_config_option("modid_my_option", "my default value", [optional key aliases])

While you can set any option, it's usually a good idea to prefix it with the mod identifier. The value of an option can be retrieved via the global function DorHUD:conf("modid_my_option"), or if you prefixed it with the same identifier as your mod, then you can use module:conf("my_option"). NOTE: This latter method only works from version 1.5.. These functions return two arguments: the first argument is the value and the second is a boolean indicating whether it was set by the user.

Now most users won't be setting config options in the user.lua file, so you can also provide menu options in your definition file:
module:add_menu_option("hud_show_player_equipment", { type = "boolean", text_id = "Option name text", help_id = "Option description to explain its purpose.", localize = false, }) module:add_menu_option("hud_show_player_downs", { type = "multi_choice", text_id = "mod_hud_show_player_downs", choices = {{"disabled", "do_not_show"},{"always", "always_show"}}, default_value = "always" }) module:add_menu_option("hud_bleedout_time_threshold", { type = "number", precision = 2, text_id = "mod_hud_bleedout_threshold", help_id = "mod_hud_bleedout_threshold_help", default_value = "3.14" }) module:add_menu_option("hud_show_extras", { type = "keybind", text_id = "mod_hud_show_extras" }) module:add_menu_option("hud_some_text_option", { type = "text", text_id = "mod_hud_some_text_option", default_value = "default text" }) module:add_menu_option("hud_downs_divider", { type = "divider", size = 5 })

If you have a lot of options, you can also lazily load the files:
module:register_include("modoptions", { type = "menu_options", lazy_load = true })
However, if you choose to do so, you have to provide the options in order to load them from the persistent storage. Moreover, default values defined with the add_menu_option function are not set as the default config values before version 1.5, instead they're only used for that menu option.


* Most things said to say to require version 1.5 are actually already usable from the latest version in the 1.4 branch.
Last edited by DorentuZ`; Feb 4, 2020 @ 11:02am