-
Notifications
You must be signed in to change notification settings - Fork 0
Profile Overview
When you use Revenant all functionality is organized by profiles.
The basic profile setup is handled in the Scripting editor of your LGS profile, which tells Revenant what the profile is called as well as how and where to load files, as seen in the reference_LGS_template.lua file.
The main part of setting up the mouse keys and the Revenant environment can then either be done by configuring a profile template either in a separate lua file (the recommended) method or within the rv.profile function in the scripting window.
The Profile template object has the following fields (although for most profiles only the key and config fields tend to be relevant):
-
key: The dictionary of Key Bindings. -
config: Global configuration for the profile. -
library: A dictionary of macros available throughout all inherited profiles. -
start: A special Macro that executes when the profile is loaded. -
exit: A special Macro that executes when the Profile is unloaded. -
documentation: The Documentation dictionary which assigns descriptions to macro names for use in Documentation Mode. -
scopeDefaults: Lets you define Profile-wide default options for any macro option. -
scopeOverride: An Experimental feature to forcefully override options on any macro on the profile. -
hooks: Advanced feature for injecting your own lua logic at specific steps in Revenant's lifecycle.
Bindings are defined in the key table, which maps key names to macros.
Usually the majority of a profile definition consists of adding macros to different buttons on the key table.
The default naming scheme for buttons combines the first letter of their family (m for "mouse", k for "keyboard" and l for "left handed controller") with the number of the programmable key according to Logitech.
Note
LGS does not allow capturing or binding functionality to normal keyboard keys, "keyboard keys" here refer to the G-keys on the Logitech keyboard.
local k = profile.key
-- Mouse button 3, middle click, will press "a".
k.m3 = "a"
-- The first G-Key on your keyboard will output "b"
k.k1 = "b"
-- The first button on your left handed controller outputs "c"
-- I'm assuming if you use all three devices you are operating one of them with your foot.
k.l1 = "c"
The usual approach of simply binding macros to key names and defining any options inside the macro is called "flat binding" and is the binding method used for most examples in this documentation.
However, there are other ways of organizing macros. The Grouping based approach lets you define groups for g-shift state, mouse mode or even custom names and options, with the individual key assignments being defined within those groups.
The following example demonstrates
Bindings in the shift_0 group are triggered when the g-shift key is not pressed, while bindings inside the shift_1 group are triggered only when g-shift is pressed.
Additionally, there is also a shift_2 group for macros that can trigger regardless of shift state.
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
--assigning the entire group at once
k.shift_0 = {
m3 = "a",
m4 = "b"
}
--assigning to a single property within a group
k.shift_1.m3 = "c"
k.shift_2.m5 = "d"
These shift-grouped bindings above are equivalent to the following "flat" bindings:
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
k.m3 = {"a", {"c", gshift = 1}}
k.m4 = "b"
k.m5 = {"d", gshift = 2}
The mode groups work the same way. Revenant will parse as as many mode_* groups as are configured for the current profile plus a special mode_0 group for macros that will trigger in all modes.
[Note that the standard autocomplete will always assume 3 modes]
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
-- Minimal config for two numeric modes instead of three.
profile.config = { globalModes = {1,2} }
-- Macros active in mode 1
k.mode_1 = {
m3 = "a",
m4 = "b"
}
-- Macros active in mode 2
k.mode_2.m3 = "c"
-- The macro for switching modes is active in all modes.
k.mode_0.m5 = {type="mode", 0}
The final and most advanced type of group is the custom group. These groups can be freely named, the only requirement is that their name needs to start with _c.
[Note that because of their arbitrary names, the standard autocomplete for macro assignments doesn't work for macros assigned inside custom groups, but if you are using them, I'll assume you're advanced enough to not need it anyways.]
What makes custom groups especially useful is that in addition to visually organizing macros, custom groups can also be used to inject macro options into all bindings that the group contains.
Caution
When working with custom groups make sure to never ever assign a key name that is also the name of a macro option, this will likely break things.
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
--- A purely visual custom group without any inheritance, containing a single macro
k._c_visual = {
m3 = "a"
}
-- A custom group that inherits the g-shift option to its children.
-- functionally, this is identical to the built-in shift_1 group
k._c_shifted = {gshift = 1}
k._c_shifted.m3 = "b"
-- A custom group that injects "type", "actionDelay" and "loop" options
k._c_sequences = {type = "sequence", actionDelay = 300, loop = 3}
k._c_sequences.m4 = {"a", "b", "c"}
k._c_sequences.m5 = {"d", "e", "f"}
The start and exit properties of are special bindings for macros that will automatically execute when a profile is loaded and unloaded without any key being pressed.
Important
Because the way LGS terminates lua scripts upon exiting a profile is a bit irregular it cannot be guaranteed that the exit binding will have time to complete or even run at all, so better not bind anything important to it.
---@type ProfileTemplate, Revenant
local profile = ...
profile.start = { "hi!", type="key" }
profile.exit = { "bye!", type="key" }
This profile will type "hi!", whenever it is loaded and "bye!" when unloaded.
The start binding is more useful in complex scenarios. For example you can use a sequence macro to loop a keypress with an area restriction in order to automatically output something the moment the mouse enters a specific portion of the screen without an additional button press.
The config property holds your Profile's configuration. These are settings that globally affect all Macros and generally define the environment, for example which types of devices are available, special designations for certain buttons, monitor resolution etc.
For a full list of available options see the Options Documentation.
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
-- Minimal config that sets the number of modes to two instead of three.
-- Also action in key outputs and sequence macros will have a standard 50ms delay.
profile.config = {
globalModes = {1,2},
actionDelay = 50
}
An external configuration is simply a lua file that only contains only a configuration table and that is loaded into the current profile using a relative path the externalConfigs option. You can find an example of such an external configuration in the repository here: start\reference_config.lua. External configurations enable you to easily share mouse set-ups and general settings between multiple profiles.
You can use external configs together with internal configs, with any internal settings overriding external ones.
An external config can itself extend via another configuration file via its externalConfig option and here too will the child settings override the parent settings if both are set.
--- Config.lua
---@type OptionsCollection
--- Just a few options we might want to set on most profiles.
return {
defaultMode = 0,
logEvents = true,
LCDPersistentProfile = true,
restrictToMainScreen = true,
rename = {
m4 = "m8",
m5 = "m7"
}
}
--- Profile.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
profile.config = {
-- the extension is optional, "Config.lua" would be valid too.
-- relative paths use a forward slash, for example "Presets/Config"
externalConfigs = "Config"
}
It is also possible to load multiple external config files into a profile by setting the externalConfigs option to a table containing a list of filenames instead of a single one.
in case the configurations contain
A profile's Library, stored in the library property, is a table of named macros that are not bound to keys.
It is designed as an organizational tool for utility macros that are then included via reference on macros on the actual bindings.
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
profile.library = {
example_seq = {"a","b","c", type="sequence"},
example_cycle = {"d","e","f", type="cycle"}
}
--- "link" or "instance" macros can be used to reference bindings from the library.
k.m3 = {"example_seq", type="link"}
--- references to library macros can also be nested in other macros.
k.m4 = {"x", {"example_cycle"}, "y", type="sequence"}
For most purposes it doesn't make a difference where a macro is initially defined but using the library table can make for much less cluttered profiles.
Library macros are also useful for Macros designed to be used or overridden by child profiles that extend the current one, as they feature some handy behaviors when inherited.
Caution
If a macro bound to a key is given the same name as a macro in the library, all name references within the current profile will prioritize the bound macro over the library macro. Just avoid duplicate names if possible.
Revenant lets you document your macros, not just for when you read the file but also on the lua console and the LCD display.
You can set your profile to Documentation mode which will, instead of performing the action on a macro, output a description of that macro on the screen or console.
The content of the field is a table, where the keys are the macro names and values are the strings used to document them.
Besides the profile's documentation object, a macro can also be documented via the documentation option directly on the macro itself. The direct option on the macro will always override the general documentation for the profile.
local profile = ...
local k = profile.key
--- The documentation object uses the targeted macro names as keys.
profile.documentation = {
macro_1 = "This macro prints 1.",
macro_2 = "This macro prints 2."
}
--- In Documentation Mode this button outputs the value from the Profile's documentation object.
k.m3 = {"1", name="macro_1"}
--- In Documentation Mode this button outputs the content of its own documentation option.
k.m4 = {"2", name="macro_2", documentation="This documentation has higher priority. The macro still prints 2."}
--- This button toggles Documentation Mode.
k.m5 = { type="documentation" }
Like configurations, a profile's documentation can be loaded via a separate file and like the external configs their contents can be overridden by local documentation definitions.
--- Docs.lua
return {
exampleMacro = "This a documentation for a macro."
}
--- Profile.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
profile.config = {
-- the extension is optional, "Docs" would be valid too.
-- relative paths use a forward slash, for example "Presets/Docs"
externalDocs = "Docs.lua"
}
--- If Revenant is in Documentation Mode, this macro will display "This a documentation for a macro."
--- A "documentation" option on this macro will override any documentation from the Docs.lua file.
k.m3 = { "example string", name="exampleMacro" }
--- This button toggles Documentation Mode.
k.m4 = { type="documentation" }
The scopeDefaults property contains a table on which you can set options for any type of macro. These options will be used as the defaults for any macro for which the option is valid unless of course the macro overrides the default by defining that options on itself.
These defaults make it possible to simplify setting up profiles containing many macros with similar options.
While some basic settings for macro timing and behavior can be defined via global options in the configuration object, this doesn't include all of them, while the scopeDefaults let you set default options for every option on every macro type.
A macro will only inherit options from the scopeDefaults that are valid for its type.
If an option is set both in the configuration and scopeDefaults, the value set in scopeDefaults is preferred.
If a macro inherits an options value from a parent such as a group macro or sequence the inherited values will override the scopeDefaults as well, as they are more specific than than the scope of the profile.
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
-- Defining our monitor resolution.
profile.config = { monitors = {1920,1080} }
-- scopeDefault is used to set a default "area" option.
profile.scopeDefaults = { area = { "50%", "100%" } }
-- both of these macros only trigger on the left half of the screen because of our scopeDefaults
k.m4 = "a"
k.m5 = "b"
-- This macro works on the whole screen because the direct area option overrides the default value.
k.m6 = { "c", area = { "100%" } }
Once you have configured a profile to fit your needs you don't need to repeat or copy your set-up for further profiles thanks to Revenant's powerful inheritance features.
This goes beyond reusing external configuration files, for example let's say we have many action games that use similar control schemes such as "e" to interact, "i" for inventory, "shift" to run, "m" for map, "r" to reload, "ctrl" to crouch, and so on for which we can create a single generic "action" profile and then have multiple specific profiles extend from in, each only defining the few bindings that are exclusive to each game.
Here is an example of my typical action game base profile:
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
profile.config = {clearLog = true, externalConfigs = "conf/defaultConfig", noMacroExtension = true}
k.m4 = "/05"
k.m5 = "/09"
k.m9 = "e"
k.m10 = "/s"
k.m11 = "i"
k.m12 = "/c"
k.m13 = "r"
k.m17 = "m"
k.m18 = "\t"
k.m20 = { { "/e", n = "esc" }, { t = "doc", g = 1 }, t = "g" }
profile.documentation = {
m4 = "+quicksave:",
m5 = "+quickload:",
m9 = "+interaction:",
m10 = "+sprint:",
m11 = "+inventory:",
m12 = "+crouch:",
m13 = "+reload:",
m17 = "+map:",
m18 = "+tab menu:",
esc = "+Escape.\nG-Shift for documentation mode"
}
Here we have all the typical keys bound to their conventional functions and I am also already using an external configuration file for all my global mouse settings.
Through inheritance my profile for Far Cry 3 only takes up 10 lines:
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
profile.config = {extends = "_defaultprofile", description = "Far Cry 3"}
k.m11 = "1"
k.m12 = "c"
k.m15 = "t"
k.m16 = "f"
k.m19 = "y"
All configurations and bindings are inherited from the parent.
Since the game mostly sticks to the usual shooter controls the profiles merely consists of adding bindings for the keys m15, m16 and m19 replacing the bindings on m11 and m12.
Preparing Profile templates for different genre is a big time saver.
By default, child macros always override parent macros on the same key, but for more complex profiles it can be useful to merge bindings through the process of "macro extension".
To enable this functionality for a profile, the "noMacroExtension" option needs to be deactivated in its configuration.
Which macro extension enabled, when a profile binds a macro to a button that also has a macro bound to it by the "parent" profile, revenant will decide whether or not to keep the child binding, based on comparing both macros' triggers.
There are five trigger relevant properties: gshift, mode, mkey, condition and area.
If any of these properties are different between the parent profile's macro and the child profiles macro then instead of the child macros binding overriding the parent binding, both macros are merged into one, so both can still trigger.
--- ProfileA.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
k.m3 = {"a",g=1}
k.m4 = "c"
--- ProfileB.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
profile.config = {extends = "ProfileA", noMacroExtension = false }
-- This m3 binding triggers differently than m3 on ProfileA
k.m3 = "b"
k.m4 = "d"
On the m3 key the parent profile's "a" binding only triggers if g-shift is active. Therefore it has a different trigger than the "b" binding of the child's m3 key, so both bindings are kept, resulting in a merged button that outputs "a" if g-shift isn't pressed and "b" if it is.
The trigger conditions for both profiles' m4 bindings are however identical, so the standard logic of only keeping the child binding is applied.
Revenant lets you chain as many inheritances as you want. ProfileC can extend ProfileB which extends ProfileA and so on.
But it is also possible to extend a profile from multiple other profiles at once that originally did not extend each other by providing the extends option as a list of names.
--- ProfileA.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
k.m3 = "a"
--- ProfileB.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
-- Note that we're not extending anything here
k.m4 = "b"
--- ProfileC.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
profile.config = { extends = { "ProfileA" , "ProfileB" } } -- Extending several profiles at once
k.m5 = "c"
If a profile extends multiple other profiles, the parent profiles are loaded in the order of the extends list, and each time the standard inheritance logic is applied, meaning that the above example behaves identical to the aforementined ProfileC extending ProfileB extending ProfileA example, even though ProfileB has no extends option set.
This is a very flexible inheritance option, as choosing in which way the macros are overridden in each profile can be adjusted simply by altering the order of the list without needing to update the files of the other profiles at all.
When multiple profiles are merged together during inheritance it is possible to end up with multiple macros with the same name on different profiles.
Since there are several functionalities like link and control macros that target other macros by name it can be important to understand how such naming conflicts are resolved.
When a Revenant checks for a reference macros it does two things: First it checks if there is a macro with the referenced name defined in the scope of the current profile.
If none is found Revenant checks if any profile contains a macro with the referenced name, working backwards from the highest profile in the inheritance chain down to the first.
Let's illustrate this with a few examples:
--- ProfileA.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
k.m3 = {"a", name="button"}
k.m4 = {type="link", "button"}
--- ProfileB.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
profile.config = {extends = "ProfileA"}
k.m5 = {"b", name="button"}
k.m6 = {type="link", "button"}
Here Profile B extends from Profile A, meaning loading it will result in a profile with 4 keybindings, two macros named "button" and two links. But which of the "button" macros do the link macros in the combined profile refer to?
The answer is that each of the links still refers to the "button" macro originally defined in the same profile, meaning the keys m3 and m4 both output "a" and m5 and m6 output "b".
When chaining several profiles during inheritance we don't even need to define all macros on all profiles in order to target them:
--- ProfileA.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
k.m3 = {"a", name="button"}
--- ProfileB.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
profile.config = {extends = "ProfileA"}
-- Here, we link to a macro that is not defined on the current profile at all
k.m4 = {type="link", "button"}
--- ProfileC.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
profile.config = {extends = "ProfileB"}
k.m5 = {"b", name="button"}
We load profile C. Both profiles A and B define a macro called "button", while profile B only contains a link to "button" without defining any macro with that name. So which one of the "button" macros is targeted by the Link macro?
The answer is the "button" macro from profile C because it is the most recently loaded child macro giving it higher priority. So in our final configuration m3 outputs "a", m4 outputs "b" and m5 outputs "b" as well.
Finally let's see how links interact with child profiles that override bindings:
--- ProfileA.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
k.m3 = {"a", name="button"}
k.m4 = {type="link", "button"}
--- ProfileB.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
profile.config = {extends = "ProfileA"}
k.m3 = {"b", name="button"}
k.m5 = {type="link", "button"}
Both profiles define a macro named "button" and link it to another key. Since the m3 "button" macro of profile B overrides the m3 binding of macro A will m5 now link to the a or b variant of "button"?
The answer is that m5 still links to the a variant of "button"; the macro may no longer be bound to m3, but even unbound macros are still parsed, as long as they are specifically named.
Otherwise an accidentally duplicated macro name on a child profile could easily disrupt up the functionality of a parent profile in ways that are hard to debug.
But in case you want to replace a macro that is both defined and referenced in a parent profile, this can be accomplished using the profile's library.
The macros that are stored in a profiles library are treated differently during inheritance than macros defined directly on a key.
Just like the bindings table, the child macro merges its own library with the library of the parent profile any if any macros share the same name, the parent's macro is overwritten with the library macro of the child profile.
But importantly, only the state of the library after all profiles are combined is parsed, meaning that the replacement of library macros can propagate backwards from the children to the parent profile.
let's demonstrate:
--- ProfileA.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
k.m3 = {type = "link", "button"}
profile.library = {
button = { "a" }
}
--- ProfileB.lua
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
profile.config = {extends = "ProfileA"}
k.m4 = { type="link", "button"}
profile.library = {
button = { "b" }
}
Here both profiles define a library macro called "button", "a" on ProfileA and "b" on ProfileB but, you will notice that on ProfileB both the inherited m3 button and m4 output b, an important difference from regular inheritance;
If profileA had defined its "button" macro as a binding on a key, the link macro on m3 would still reference this variant of the macro and output "a" and ignore the newer "button" macro on the child profile.
The following fields offer advanced functionality that only the most ambitious profiles should require.
Like the name suggests it will completely override any setting on the macros itself, scopeDefaults or Profile configuration. Usually only used for testing and debugging profiles.
---@type ProfileTemplate, Revenant
local profile = ...
local k = profile.key
profile.config = {extends = "ProfileA"}
A number of event hooks that allow the injection of custom logic at specific points in the script. See Advanced Lua integration.
- Key Macro
- Sequence Macro
- Cycle Macro
- Group Macro
- Multiclick Macro
- Hold Key Macro
- Mouse Position Macro
- Mouse Wheel Macro
- Mode Change Macro
- Backlight Macro
- DPI Macro
- External Macro
- Log Macro
- Documentation Macro
- Pagination Macro
- Control Macro
- Key Buffer Macro
- Wrap Key Macro
- Link Macro
- Instance Macro
- Flag Macro
- Function Macro
- Alter History Macro