# Code Documentation for Lazarr!

This file gives a rough overview about the code structure
of Lazarr! Individual mods may provide more detail.

## Overview

The heart of the game is provided in `mods/lzr_laser`. Here is the code
that makes the lasers work, plus it adds various 'laser blocks', i.e.
blocks that interact with lasers (mirrors, detectors, etc.).

Lasers are nodes. For each possible laser intersection, there is a node.
If you like to see all nodes, try enabling Development Mode
(see below).
Additionally, some nodebox-based nodes like slabs, panes, empty chests,
etc. also come with laser variants. These are used when a laser
meets these nodes from a particular direction.
Great care has been put into the node design to make the lasers
look as "natural" in the world as possible, avoiding seams and
other problems (see "Laser compatibility" below).

Lasers are updated whenever a relevant change was made to the world.
This is called a 'laser update'. A laser update always recalculates all
the lasers from scratch. While this obviously has room for speed
optimization, a laser update appears to be still reasonably fast.
The game uses LuaVoxelManip for laser updates.

The level logic is in `mods/lzr_levels`.
This mod is responsible for level organization, level packs,
loading levels and placing them into the map, removing a level
from the map, starting levels, checking the victory condition and storing the
player progress (for levels). `mods/lzr_levels_core` contains
the actual levels of this game.
Read `LEVEL_PACKS.md` for the level packs format.

The trigger system is in `mods/lzr_triggers`. Triggers are for wireless
signals between laser blocks, e.g. a detector sending a signal to
a locked chest in order to unlock it.

`mods/lzr_mapgen` is a custom map generator that generates the ocean and
islands areas in the world. It essentially serves as a backdrop
for the levels. This mod provides functions to manually regenerate
a piece of the map, so levels that have been quit can be
cleaned up.

The main ship is in `mods/lzr_menu`. The editor is in `mods/lzr_editor`.
Most non-special nodes are in `mods/lzr_core` and `mods/lzr_decor`.

There are many other mods that put everything else together.
The `description` in `mod.conf` should explain their purpose.

## Development Rules

Some general rules for developing this game apply:

1. **Nodes**
    1. All nodes incompatible with lasers MUST have
      the `laser_incompatible=1` group (see below)
    2. Nodes MUST NOT drop multiple items (for `lzr_protection`)
2. **Lasers**
    1. The "ends" of any laser node MUST NOT be visibly
       exposed to the player;
       they MUST either connect to another laser beam,
       or touch the side of a laser-compatible node.
       (thus rule does not apply in the editor or development mode)
    2. All lasers SHOULD look like a clean continuous beam;
       there SHOULD NOT be visible seams between
       laser nodes or anywhere else
    3. The 3 primary laser colors are red, green and blue
    4. Secondary colors are created by mixing:
        * red + green = yellow,
        * red + blue = magenta,
        * green + blue = cyan,
        * red + green + blue = white.
    5. Two colors are mixed by doing a logical OR between
       their primary color components. For example,
       red + yellow = yellow because yellow is made out of
       red and green.
3. **Levels**
    1. Levels must not use nodes with the `laser_incompatible` group
    2. Levels must have exactly one start block
    3. Levels must have at least one treasure chest
    4. Levels should not have tiny holes in which the player can get stuck in
    5. Levels must pass other quality tests (`/editor_check` command)
4. **Color**
    1. Color must never be the only way for players to distinguish
       puzzle-relevant information. Colorblind people must be able
       to solve all puzzles
    2. If color absolutely cannot be avoided, add an option to
       "mark" said color, e.g. by special setting
    3. These rules do not apply for colors that are not relevant
       for the solution of puzzles
5. **Technical**
    1. Don't call `set_physics_override` directly, use the `playerphysics` mod instead
    2. Only the `playerphysics` mod is allowed to call `set_physics_override`
    3. Do not add `rotate = true` to object selectionboxes


Reason for rule 5.3: This may mess up the hook rotation arrows when pointing at objects,
as of Luanti 5.13.0. This rule can be removed if hook rotation arrows can be made
to disappear properly when pointing at such objects.

### Laser compatibility

A node is said to be 'laser-compatible' when the visible
laser beam will never end at a gap when it collides with
the node from any side. Opaque full-cube nodes are
laser-compatible, while air is laser-incompatible.
Other drawtypes may or may not be compatible, depending
on their shape and texture. To see if a node is compatible,
shoot a laser to each of the node's sides. If the "end" of the
laser is visibly exposed, i.e. not touching the node's visual
border, it is incompatible. Otherwise, it is compatible.

Incompatible nodes are thus nodes where when the laser collides,
there is a visible gap between the laser beam and the visual
node.

Exception 1: If the node is immediately destroyed on contact
or transformed into a compatible node, it counts as
compatible.

Incompatible nodes can be also made compatible by adding
variants of the node with a laser beam inside of them,
and adding them as laser blocks in `lzr_laser`.

This is done for slabs and panes, for example.

#### Special case: Water

Water and other liquids are counted as laser-compatible
as a special exception, provided the laser always touches
the texture.

Technically, water would normally count as incompatible
because you can see the open end of a laser when looking
at it from inside the water. However, this is quite subtle
due to the semi-transparency. Therefore, water nodes are
allowed in levels.

## Translations

Translation in this game are done using gettext PO files, which are
natively supported in Luanti.
Translators can translate the game online at
<https://translate.codeberg.org/projects/lazarr/>.

While most mods are straight-forward with regards to translation (see
the Luanti documentation for the basics), some mods need special attention to
keep their translation working. See `DEV_TRANSLATION_WORKFLOW.md` for details.

## Node special fields reference
This is a list of special fields for node definitions that Lazarr! recognizes:

* `_lzr_active`: If the node has an 'active' counterpart, this contains the
    itemstring of that 'active' counterpart.
    Used by blocks that interact with lasers
* `_lzr_inactive`: If the node has an 'inactive' counterpart, this contains the
    itemstring of that 'inactive' counterpart.
    Used by blocks that interact with lasers
* `_lzr_fixed`: Node name of fixed (unmovable) variant of a laser block
* `_lzr_rotatable`: Node name of rotatable variant of a laser block (if it exists)
* `_lzr_takable`: Node name of takable variant of a laser block (if it exists)
* `_lzr_next_color`: Contains the name of the node with the next color in a
    sequence of colors, for the color change tool
* `_lzr_prev_color`: Same as `_lzr_next_color`, but for the previous color
* `_lzr_on_toggle(pos, node, signal)`: Function for receiver nodes. It's called when
    this node receives a signal and its state (active or inactive) will
    be changed by this. This can happen either by a tool
    or a signal sent by another node.
    `pos` is node position and `node` is a node table.
    `signal` is the incoming signal type (`"ON"`, `"OFF"` or `"TOGGLE"`).
    MUST NOT manipulate the map.
    Must return a list of actions or nil (for no action). Actions
    a table of the form `{type = <action type>, ...}` and possibly one
    additional field, depending on the action type. Available action types are:
  * `set_node`: Call `minetest.set_node` with `node`
  * `swap_node`: Call `minetest.swap_node` with `node`
  * `sound_play`: Call `minetest.sound_play` with `sound` as a SimpleSoundSpec
  * `add_particlespawner`: Call `minetest.add_particlespawner` with `particlespawner` as the definition
  * `remove_trigger`: Remove any trigger associated with node. No additional field.
    Should only be used when a previous action changed the node to a node
    that can't trigger or be triggered (e.g. dirt, air, etc.)
* `_lzr_call_always_on_toggle`: Normally, `_lzr_on_toggle` is only called
    on state changes, e.g. if an inactive receiver node receives
    a ON signal. If this field is set to `true`, then `_lzr_on_toggle`
    will be called for any incoming signals instead, no matter if they
    change the receiver state. Default: `false`
* `_lzr_on_toggle_item(holding_player, item, slot, signal)`: Same as
    `lzr_on_toggle`, but when the node is in item form in a player's
    inventory.
    `holding_player` is the player holding the item, `item` is the
    ItemStack to toggle, `slot` is the inventory slot in which the
    item resides (in the "main" list). MUST return the modified
    (or unmodified) ItemStack
* `_lzr_send_treasure(pos, node, delay)`: Function is called on open chests
    with treasure to start an animation that "sends" the treasure into
    the sky. Used when a level is completed. MUST NOT be
    called multiple times for the same chest.
    `pos` is node position, `node` is node table. If `delay` is set,
    the treasure is sent after this many seconds. Otherwise,
    it is instant
* `_lzr_plant_on_ground_basenode`: Used for rooted plant nodes
    (`rooted_plantlike`). This is to refer to the "base" node, the
    cube below the plant.
* `_lzr_plant_on_ground_baseplant` = Used for rooted plant nodes
    (`rooted_plantlike`). This is to refer to the "plant", the
    thing above the node’s base cube.

## Groups reference

See `GROUPS.md` for a reference of item groups used in this game.

## Debug Mode

There is a hidden Debug Mode in this game. It is used to
enable several debugging and testing features of the game.

To enable the hidden debug mode, set the hidden setting `lzr_debug` to
`true` in `minetest.conf`. This will unlock the following features:

### Laser debug

* Chat commands:
    * `/set_freeze_lasers`: Enable/disable laser updates on map change
    * `/force_laser_update`: Recalculate all lasers
    * `/clear_lasers`: Removes lasers
    * `/emit_lasers [<max. iterations>]`: Emits lasers from emitters
* Tools (Only via `/giveme`)
    * Laser Absorber (`lzr_tools:laser_absorber`): Digs lasers
    * Laser Stepper (`lzr_tools:laser_stepper`): Simulates laser travel up to
      a set number of iterations. Basically a more convenient method for
      `/emit_lasers`

### Trigger list

With `/show_triggers` you can display a list of all triggers
used in the current level.
This feature is mostly just there to test if the game still has
a valid state. Faulty triggers will have an exclamation mark.
In the normal game, no trigger should ever become faulty.

### Development mode

Development mode can be accessed with the `/devmode` command. In this mode,
the world can be altered more freely without the game getting in the way.
Lasers are always frozen in development mode. You can also get all items
in the inventory. This mode was created to build the pirate ship
and to test out nodes in their "pure" form.

WARNING: The world in Lazarr! is not saved. So if you make changes to the
world directly, all changes will be lost when the game is closed.
Use a schematic exporting tool to save changes.

### Solutions recording and testing

You can record solutions for levels and replay them. Solutions record
player interactions in a level in order to replay them for testing
the levels for regressions. The solution system is limited, however,
it only records interactions with nodes but not player movement.

The main use case for this feature is to record solutions for
level packs, most importantly the core levels.

This feature is useful to test and verify if the levels are still solvable
in a later update in case the behavior of a laser block was accidentally
changed in a compatibility-breaking manner. Passing the test does
*not* guarantee the level is error-free. In particular, if the player
would have to move to a place that is unreachable, the test won't
detect that.

Commands:

* `/record_solution`: Start record a solution for the current level.
  You MUST call this at the beginning of a level, not afterwards.
  Try to solve the level with as few node interactions as you can
  (the solution doesn't have to be perfect tho).
  The recording ends automatically when the level is completed and
  a file is saved into the world directory on success.
  WARNING: Existing files will be silently overwritten!
* `/replay_solution`: Replay the solution for the current level. This
  only works for level pack levels and only when you're at the beginning
  of a level.
* `/test_pack_solutions`: Mass-test ALL levels of a level pack in sequence



## Modding

This game supports limited modding.

The most straight-forward kind of mod is a level pack.
Level packs are mod, see `LEVEL_PACKS.md` to learn how to make one.

As for modding other aspects of the game, most functions
aren’t documented very well yet. The more important
functions are documented in a large code comment above the function.

Lazarr! tries to keep public-facing functions backwards-compatible
as long as possible. In case you want to make a mod but Lazarr! doesn’t
make it possible, feel free to make a feature request.

Please follow the Development Rules (see above) when developing
a mod for Lazarr!


## The Lazarr! website

Lazarr! has an official website at <https://wuzzy.codeberg.page/Lazarr/>.

You can access the source repository at the `pages` branch of
the Lazarr! git repository.
