forked from minetest-mods/player_monoids
Branching system (#10)
This commit enhances the player_monoids library by adding branching support and an improved testing system to make sure it works, as proposed in #9 . These changes enable isolated player state management and improve library reliability. This PR focuses on implementing the branching proposal and adding automated tests.
This commit is contained in:
221
API.md
221
API.md
@ -1,76 +1,169 @@
|
||||
#Player Monoids
|
||||
# Player Monoids Library Documentation
|
||||
|
||||
The idea behind this library is that global player state (physics overrides,
|
||||
armor values, etc.) changes from multiple mods should mesh nicely with each
|
||||
other. This means they must be combinable in a sane way.
|
||||
|
||||
Monoids
|
||||
=======
|
||||
A player monoid covers a single kind of player state a mod might want to change.
|
||||
These can be built-in player state, like speed multipliers or fly permissions,
|
||||
or could be custom state introduced by mods, such as corruption or reputation
|
||||
level. When you make a player monoid, you must choose some type of value to
|
||||
represent state changes - for example, numbers for speed multipliers, or vectors
|
||||
for "lucky direction". Each mod can contribute different changes, represented
|
||||
by this type of value, and they are all combined together. This combined value
|
||||
is interpreted and converted into actual effects on the player's state.
|
||||
Privileges could be set, physics overrides would be used to effect speed
|
||||
changes, and a mod might change some value to match the monoid.
|
||||
|
||||
Definition
|
||||
----------
|
||||
A player monoid definition is a table with the following:
|
||||
## Table of Contents
|
||||
1. [Introduction](#introduction)
|
||||
- 1.1 [Use Cases and Types](#use-cases-and-types)
|
||||
- 1.2 [Definition Structure](#definition-structure)
|
||||
2. [Branch System](#branch-system)
|
||||
3. [API Reference](#api-reference)
|
||||
|
||||
* ```combine(elem1, elem2)``` - An associative binary operation
|
||||
* ```fold({elems})``` - Equivalent to combining a whole list with ```combine```
|
||||
* ```identity``` - An identity element for ```combine```
|
||||
* ```apply(value, player)``` - Apply the effect represented by ```value```
|
||||
to ```player```
|
||||
* ```on_change(val1, val2, player)``` - Do something when the value on a
|
||||
player changes. (optional)
|
||||
|
||||
Additionally, you should document what values are valid representatives of
|
||||
your monoid's effects. When something says that a value is "in a monoid", it
|
||||
means that value is a valid representative of your monoid's effects.
|
||||
|
||||
combine and fold
|
||||
----------------
|
||||
```combine``` should take two values in your monoid and produce a third value in
|
||||
your monoid. It should also be an associative operation. ```fold`` should take a
|
||||
table containing elements of your monoid as input and combine them together in
|
||||
key order. It should be equivalent to using ```combine``` to combine all the
|
||||
values together. For example, ```combine``` could multiply two speed multipliers
|
||||
together, and ```fold``` could multiply every value together.
|
||||
## Introduction
|
||||
|
||||
identity
|
||||
--------
|
||||
```identity```, when combined with any other value, should result in the other
|
||||
value. It also represents the "default" or "neutral" state of the player, and
|
||||
will be used when there are no status effects active for a particular monoid.
|
||||
For example, the identity of a speed monoid could be the multiplier ```1```.
|
||||
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.
|
||||
|
||||
apply
|
||||
-----
|
||||
```apply``` is the function that interprets a value in your monoid to do
|
||||
something to the player's state. For example, you could set a speed multiplier
|
||||
as the speed physics override for the player.
|
||||
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.
|
||||
|
||||
Functions
|
||||
=========
|
||||
```player_monoids.make_monoid(monoid_def)``` - Creates a new monoid that can be
|
||||
used to make changes to the player state. Returns a monoid.
|
||||
### Use Cases and Types
|
||||
|
||||
Monoid Methods
|
||||
--------------
|
||||
```monoid:add_change(player, value[, "id"])``` - Applies the change represented
|
||||
by ```value``` to ```player```. Returns an ID for the change. If the optional
|
||||
string argument ```"id"``` is supplied, that is used as the ID instead, and any
|
||||
existing change with that ID is removed. IDs are only guaranteed to be unique
|
||||
per-player. Conversely, you are allowed to make multiple changes with the same
|
||||
ID as long as they are all on different players.
|
||||
Monoids are useful for managing both built-in player attributes and custom mod-defined states. For example:
|
||||
|
||||
```monoid:del_change(player, id)``` - Removes the change with the given ID, from
|
||||
the given player, if it exists.
|
||||
- **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.
|
||||
|
||||
```monoid:value(player)``` - The current combined value of the monoid for the
|
||||
given player.
|
||||
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:
|
||||
|
||||
```lua
|
||||
{
|
||||
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:
|
||||
|
||||
```lua
|
||||
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:
|
||||
|
||||
```lua
|
||||
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:
|
||||
|
||||
```lua
|
||||
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:
|
||||
|
||||
```lua
|
||||
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 monoid’s behavior (see [Definition Structure](#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.
|
||||
|
Reference in New Issue
Block a user