player_monoids/API.md
2025-02-06 23:48:19 +01:00

8.2 KiB
Raw Blame History

Player Monoids Library Documentation

Table of Contents

  1. Introduction
  2. Branch System
  3. API Reference

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 that combine(a, combine(b, c)) should be equivalent to combine(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 applying combine 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
  • 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 to true, ensures that on_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 monoids 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 branchs current combined value for a specific player.
    • get_name(): retrieves this branchs 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.