8.2 KiB
Player Monoids Library Documentation
Table of Contents
Introduction
The Player Monoids Library is designed to solve the problem of conflicting player state changes in Luanti when multiple mods are involved. For example, one mod might want to increase a player's speed, while another mod reduces it. Without a structured way to combine these changes, mods can overwrite each other's effects, leading to unpredictable behavior.
This library introduces monoids, which represent specific aspects of the player state, such as speed modifiers, jump height, or even custom states like corruption levels or reputation systems. Monoids allow changes from multiple mods to be combined consistently and predictably. Additionally, the library supports branches, which isolate changes into separate contexts. This makes it possible to maintain different states for different scenarios, such as minigames or alternate dimensions.
Use Cases and Types
Monoids are useful for managing both built-in player attributes and custom mod-defined states. For example:
- Built-in Attributes: Monoids can manage physics overrides like speed multipliers, jump height modifiers, or gravity changes. They can also handle privilege management (e.g., enabling or disabling fly or noclip combining booleans with the or operator) or armor values.
- Custom Mod States: Mods can define their own monoids for features like corruption levels, reputation systems, or environmental effects. For instance, you could create a monoid that tracks "lucky directions" as vectors.
Monoids can be categorized based on how they combine values:
- Multiplicative Monoids: Combine values using multiplication (e.g., speed multipliers).
- Additive Monoids: Combine values using addition (e.g., armor bonuses).
- Custom Logic Monoids: Use custom logic to combine values (e.g., vectors for directional effects).
Definition Structure
A monoid is defined as a Lua table that specifies how values are combined, applied to the player, and managed. The structure includes the following fields:
{
combine = function(elem1, elem2), -- Combines two elements (must be associative)
fold = function({elems}), -- Combines multiple elements
identity = value, -- Neutral/default value
apply = function(value, player), -- Applies the combined value to the player
on_change = function(old, new, player, branch), -- Optional callback for value changes
listen_to_all_changes = boolean -- Optional; enables branch-wide callbacks
}
Each field plays a specific role in defining the behavior of the monoid:
combine
defines how two values are merged. The function must be associative, meaning thatcombine(a, combine(b, c))
should be equivalent tocombine(combine(a, b), c)
. For example, in a speed multiplier monoid:
combine = function(a, b) return a * b end
fold
combines multiple values at once by applyingcombine
iteratively. It processes a table of values and merges them into one:
fold = function(t)
local result = 1
for _, v in pairs(t) do result = result * v end
return result
end
-
identity
is the neutral default value that will be used when there are no status effects active for a particular monoid. When combined with any other value, it leaves it unchanged. For example:- Speed multipliers:
identity = 1.0
- Additive bonuses:
identity = 0
- Speed multipliers:
-
apply
translates the combined monoid value into actual effects on the player's state:
apply = function(multiplier, player)
player:set_physics_override({speed = multiplier})
end
on_change
is an optional callback triggered whenever the monoid's value changes for a player:
on_change = function(old_val, new_val, player, branch)
local branch_name = branch:get_name()
core.log("Speed changed from " .. old_val .. " to " .. new_val .. " on branch " .. branch_name)
end
-
listen_to_all_changes
, when set totrue
, ensures thaton_change
is triggered for all branch updates instead of just the active branch. -
on_branch_created(monoid, player, branch_name)
: Optional callback, called when a new branch is created. -
on_branch_deleted(monoid, player, branch_name)
: Optional callback, called when a branch is deleted.
Branch System
Branches allow mods to isolate state changes into separate contexts without interfering with each other. Each branch maintains its own set of modifiers and can be activated independently.
By default, every player starts on the "main"
branch. This branch represents their normal state and is created automatically when a monoid is initialized. Additional branches can be created and accessed in three ways:
- Using
monoid:new_branch(player, name)
to create a new branch without activating it - Using
monoid:checkout_branch(player, name)
to switch the player's active branch, creating it if needed - Using
monoid:get_branch(name)
to get a wrapper for managing the branch at any time, or false if it doesn't exist
When switching branches with checkout_branch
, the player's state is immediately updated to reflect the combined value of the new active branch.
The inactive branches can still be modified in the background, but their combined values won't affect the player's state until they get activated.
API Reference
player_monoids.make_monoid(monoid_def)
The monoid
object mentioned in this API's methods has to first be created using this function. monoid_def
is a table defining the monoid’s behavior (see Definition Structure).
monoid:add_change(player, value[, id, branch_name])
Applies a change represented by value
to the player. Takes a player
object, a value
parameter that must be valid for this monoid, an optional branch-unique string id
(if not provided, a random one will be generated), and an optional branch_name
parameter (if not provided, the "main"
branch will be used). Returns the ID of the added change.
monoid:del_change(player, id[, branch_name])
Removes the change represented by id
from the player. If branch_name
is not provided, the "main"
branch will be used.
monoid:value(player[, branch_name])
Gets the value of this monoid for a specific player and branch. Takes a player object and an optional branch_name
parameter. If branch_name
is not provided, the active branch will be used. Returns the combined value for this monoid.
monoid:new_branch(player, branch_name)
Creates a new branch for a player, but does not switch to it. Returns the handler object for the new branch.
- The returned handler provides methods for managing changes specific to that branch:
add_change(player, value[, id])
: adds a change to this specific branch.del_change(player, id)
: removes a change from this specific branch by its ID.value(player)
: gets this branch’s current combined value for a specific player.get_name()
: retrieves this branch’s name as a string.reset(player)
: clears all changes on this branch for the specified player.delete(player)
: deletes this branch for the specified player. If the deleted branch is the active branch, the active branch will be switched to"main"
. You can't delete the main branch.
mononid:checkout_branch(player, name)
Switches the player's active branch to the specified one, creating it if it doesn't exist. Returns the handler object for the new branch.
monoid:get_active_branch(player)
Gets a handler object representing the player's currently active branch.
monoid:get_branch(name)
Retrieves a handler object for the specified branch. Returns false
if the branch does not exist.
monoid:get_branches(player)
Returns a table of branch wrappers, keyed by branch name, for all branches associated with the player.
monoid:reset_branch(player[, branch_name])
Clears all changes associated with a player's branch. If no branch name is provided, it resets "main"
by default.