439 lines
9.9 KiB
Markdown
439 lines
9.9 KiB
Markdown
# MATRIX
|
||
|
||
A rectangular grid of cells with a small built-in stone-falling physics system and primitive enemy pathfinding. Designed around a single use case — the digging minigame (Kretes sections in *Reksio i Ufo*: the wall on Indor and the prison-tunnel on Kuran).
|
||
|
||
Grid data lives in a one-dimensional array of integers — a cell's address is computed as `index = y * width + x`. Hence the family of methods that converts between linear index and 2D position.
|
||
|
||
## Field codes
|
||
|
||
Values used by [`GET`](#get), [`GETCELLSNO`](#getcellsno), [`SET`](#set), [`SETROW`](#setrow) and the internal physics system:
|
||
|
||
| Code | Meaning |
|
||
| --- | --- |
|
||
| `0` | empty cell |
|
||
| `1` | ground |
|
||
| `2` | stone |
|
||
| `3` | dynamite stick |
|
||
| `4` | weak (destructible) wall |
|
||
| `5` | enemy |
|
||
| `6` | strong (indestructible) wall |
|
||
| `7` | lit dynamite stick |
|
||
| `8` | cell within blast radius |
|
||
| `9` | exit |
|
||
| `99` | Kretes' position |
|
||
|
||
## Movement direction codes
|
||
|
||
Values used by [`TICK`](#tick), [`NEXT`](#next), and [`CALCENEMYMOVEDIR`](#calcenemymovedir):
|
||
|
||
| Code | Direction |
|
||
| --- | --- |
|
||
| `0` | left |
|
||
| `1` | up |
|
||
| `2` | right |
|
||
| `3` | down |
|
||
|
||
The physics system uses additional codes returned by [`TICK`](#tick) and consumed by [`NEXT`](#next):
|
||
|
||
| Code | Operation |
|
||
| --- | --- |
|
||
| `1` | stone falls down |
|
||
| `2` | stone slides diagonally to the left |
|
||
| `3` | stone slides diagonally to the right |
|
||
| `4` | stone collides with an enemy and explodes |
|
||
|
||
## Fields
|
||
|
||
### BASEPOS
|
||
|
||
```
|
||
INTEGER, INTEGER BASEPOS
|
||
```
|
||
|
||
Pixel position of the grid's top-left corner on screen (`X,Y`).
|
||
|
||
### CELLHEIGHT
|
||
|
||
```
|
||
INTEGER CELLHEIGHT
|
||
```
|
||
|
||
Height of a single cell in pixels.
|
||
|
||
### CELLWIDTH
|
||
|
||
```
|
||
INTEGER CELLWIDTH
|
||
```
|
||
|
||
Width of a single cell in pixels.
|
||
|
||
### SIZE
|
||
|
||
```
|
||
INTEGER, INTEGER SIZE
|
||
```
|
||
|
||
Grid dimensions in cells (`width,height`). The value also dictates the size of the internal field-code array.
|
||
|
||
## Methods
|
||
|
||
### CALCENEMYMOVEDEST
|
||
|
||
```
|
||
INTEGER CALCENEMYMOVEDEST(INTEGER oldCell, INTEGER direction)
|
||
```
|
||
|
||
Computes the destination cell for an enemy moving from `oldCell` in the given `direction`. If the target cell is out of bounds or not empty, `oldCell` is returned unchanged.
|
||
|
||
**Parameters**
|
||
|
||
- `oldCell` — the enemy's current cell index.
|
||
- `direction` — movement direction (`0`–`3`, see [Movement direction codes](#movement-direction-codes)).
|
||
|
||
**Returns**: [`INTEGER`](INTEGER.md) — destination cell index.
|
||
|
||
**Examples**
|
||
|
||
```
|
||
MAT^CALCENEMYMOVEDEST(IENOLDCELL,IENNEWDIR);
|
||
```
|
||
|
||
### CALCENEMYMOVEDIR
|
||
|
||
```
|
||
INTEGER CALCENEMYMOVEDIR(INTEGER oldCell, INTEGER oldDir)
|
||
```
|
||
|
||
Computes the next movement direction for an enemy. The algorithm prefers turning left: it first checks `(oldDir+3) MOD 4`, then `oldDir`, then right, and finally backwards. The first direction whose target cell is empty is returned; if none is available, the leftward direction is returned anyway.
|
||
|
||
**Parameters**
|
||
|
||
- `oldCell` — the enemy's current cell index.
|
||
- `oldDir` — the previous-step movement direction.
|
||
|
||
**Returns**: [`INTEGER`](INTEGER.md) — the new movement direction.
|
||
|
||
**Examples**
|
||
|
||
```
|
||
MAT^CALCENEMYMOVEDIR(IENOLDCELL,IENOLDDIR);
|
||
```
|
||
|
||
### CANHEROGOTO
|
||
|
||
```
|
||
BOOL CANHEROGOTO(INTEGER cellNo)
|
||
```
|
||
|
||
Checks whether the protagonist can step onto the given cell. A cell is walkable if it does not contain a weak wall, strong wall, enemy or stone, and is not part of a gate area set with [`SETGATE`](#setgate).
|
||
|
||
**Parameters**
|
||
|
||
- `cellNo` — index of the cell to check.
|
||
|
||
**Returns**: [`BOOL`](BOOL.md) — `TRUE` if the cell is walkable.
|
||
|
||
**Examples**
|
||
|
||
```
|
||
MAT^CANHEROGOTO(I_0);
|
||
```
|
||
|
||
### GET
|
||
|
||
```
|
||
INTEGER GET(INTEGER cellNo)
|
||
```
|
||
|
||
Returns the field code of the cell at the given index. For out-of-range indices returns `0`.
|
||
|
||
**Parameters**
|
||
|
||
- `cellNo` — cell index.
|
||
|
||
**Returns**: [`INTEGER`](INTEGER.md) — field code (see [Field codes](#field-codes)).
|
||
|
||
**Examples**
|
||
|
||
```
|
||
MAT^GET(I_ITERATOR);
|
||
MAT^GET(I_MOLE_FIELD_CURR);
|
||
```
|
||
|
||
### GETCELLOFFSET
|
||
|
||
```
|
||
INTEGER GETCELLOFFSET(INTEGER x, INTEGER y)
|
||
```
|
||
|
||
Returns the cell index for the given `(x, y)` grid coordinates. Returns `-1` if the coordinates fall outside the grid.
|
||
|
||
**Parameters**
|
||
|
||
- `x`, `y` — grid coordinates.
|
||
|
||
**Returns**: [`INTEGER`](INTEGER.md) — the cell index or `-1`.
|
||
|
||
**Examples**
|
||
|
||
```
|
||
MAT^GETCELLOFFSET(ISSRCX,ISSRCY);
|
||
MAT^GETCELLOFFSET(ISTRGX,ISTRGY);
|
||
```
|
||
|
||
### GETCELLPOSX
|
||
|
||
```
|
||
INTEGER GETCELLPOSX(INTEGER cellNo)
|
||
```
|
||
|
||
Returns the cell's X position in pixels (`BASEPOS.X + col * CELLWIDTH`).
|
||
|
||
**Parameters**
|
||
|
||
- `cellNo` — cell index.
|
||
|
||
**Returns**: [`INTEGER`](INTEGER.md) — X coordinate in pixels.
|
||
|
||
### GETCELLPOSY
|
||
|
||
```
|
||
INTEGER GETCELLPOSY(INTEGER cellNo)
|
||
```
|
||
|
||
Returns the cell's Y position in pixels (`BASEPOS.Y + row * CELLHEIGHT`).
|
||
|
||
**Parameters**
|
||
|
||
- `cellNo` — cell index.
|
||
|
||
**Returns**: [`INTEGER`](INTEGER.md) — Y coordinate in pixels.
|
||
|
||
### GETCELLSNO
|
||
|
||
```
|
||
INTEGER GETCELLSNO(INTEGER cellCode)
|
||
```
|
||
|
||
Returns the number of cells with the given field code.
|
||
|
||
**Parameters**
|
||
|
||
- `cellCode` — the field code to count.
|
||
|
||
**Returns**: [`INTEGER`](INTEGER.md) — number of matching cells.
|
||
|
||
**Examples**
|
||
|
||
```
|
||
MAT^GETCELLSNO(IC_FIELD_CODE_EXIT);
|
||
MAT^GETCELLSNO(IC_FIELD_CODE_ENEMY);
|
||
```
|
||
|
||
### GETFIELDPOSX
|
||
|
||
```
|
||
INTEGER GETFIELDPOSX(INTEGER cellNo)
|
||
```
|
||
|
||
Alias of [`GETCELLPOSX`](#getcellposx), used in *Reksio i Ufo* with the Piklib 7.1 engine. Piklib 8 no longer ships this method — calling it crashes the engine.
|
||
|
||
**Parameters**
|
||
|
||
- `cellNo` — cell index.
|
||
|
||
**Returns**: [`INTEGER`](INTEGER.md) — X coordinate in pixels.
|
||
|
||
### GETFIELDPOSY
|
||
|
||
```
|
||
INTEGER GETFIELDPOSY(INTEGER cellNo)
|
||
```
|
||
|
||
Alias of [`GETCELLPOSY`](#getcellposy), used in *Reksio i Ufo* with the Piklib 7.1 engine. Piklib 8 no longer ships this method — calling it crashes the engine.
|
||
|
||
**Parameters**
|
||
|
||
- `cellNo` — cell index.
|
||
|
||
**Returns**: [`INTEGER`](INTEGER.md) — Y coordinate in pixels.
|
||
|
||
### GETOFFSET
|
||
|
||
```
|
||
INTEGER GETOFFSET(INTEGER x, INTEGER y)
|
||
```
|
||
|
||
Alias of [`GETCELLOFFSET`](#getcelloffset), used in *Reksio i Ufo* with the Piklib 7.1 engine. Piklib 8 replaces it with `GETCELLOFFSET`.
|
||
|
||
**Parameters**
|
||
|
||
- `x`, `y` — grid coordinates.
|
||
|
||
**Returns**: [`INTEGER`](INTEGER.md) — cell index or `-1`.
|
||
|
||
### ISGATEEMPTY
|
||
|
||
```
|
||
BOOL ISGATEEMPTY()
|
||
```
|
||
|
||
Checks whether all cells in the gate area set with [`SETGATE`](#setgate) have field code `0` (empty). Returns `FALSE` if no gate has been set.
|
||
|
||
**Returns**: [`BOOL`](BOOL.md) — `TRUE` if all gate cells are empty.
|
||
|
||
**Examples**
|
||
|
||
```
|
||
MAT^ISGATEEMPTY();
|
||
```
|
||
|
||
### ISINGATE
|
||
|
||
```
|
||
BOOL ISINGATE(INTEGER cellNo)
|
||
```
|
||
|
||
Checks whether the cell at the given index belongs to the gate area set with [`SETGATE`](#setgate).
|
||
|
||
**Parameters**
|
||
|
||
- `cellNo` — cell index.
|
||
|
||
**Returns**: [`BOOL`](BOOL.md) — `TRUE` if the cell is inside the gate area.
|
||
|
||
### MOVE
|
||
|
||
```
|
||
void MOVE(INTEGER oldCell, INTEGER newCell)
|
||
```
|
||
|
||
Moves the contents of the source cell to the destination cell; the source cell becomes empty (code `0`).
|
||
|
||
**Parameters**
|
||
|
||
- `oldCell` — source cell index.
|
||
- `newCell` — destination cell index.
|
||
|
||
**Examples**
|
||
|
||
```
|
||
MAT^MOVE(I_0,I_1);
|
||
```
|
||
|
||
### NEXT
|
||
|
||
```
|
||
INTEGER NEXT()
|
||
```
|
||
|
||
Executes the next queued move generated by [`TICK`](#tick). The source cell is cleared and the destination cell receives the stone code. After the move the [`ONNEXT`](#onnext) signal is emitted for every move except the last one in the queue, which fires [`ONLATEST`](#onlatest) instead.
|
||
|
||
**Returns**: [`INTEGER`](INTEGER.md) — `0` if the queue is empty; `1` for an ordinary stone move; `2` if the stone has come to rest two cells above Kretes (collision).
|
||
|
||
**Examples**
|
||
|
||
```
|
||
MAT^NEXT();
|
||
```
|
||
|
||
### SET
|
||
|
||
```
|
||
void SET(INTEGER cellNo, INTEGER cellCode)
|
||
void SET(INTEGER x, INTEGER y, INTEGER cellCode)
|
||
```
|
||
|
||
Sets a cell's field code. Two forms are accepted: the first takes a precomputed cell index, the second takes `(x, y)` coordinates.
|
||
|
||
**Parameters**
|
||
|
||
- `cellNo` — cell index **(form 1)**.
|
||
- `x`, `y` — cell coordinates **(form 2)**.
|
||
- `cellCode` — new field code (see [Field codes](#field-codes)).
|
||
|
||
**Examples**
|
||
|
||
```
|
||
MAT^SET(I_MOLE_FIELD_CURR,IC_FIELD_CODE_EMPTY);
|
||
MAT^SET([I_FIELD_INDEX%I_LEVEL_WIDTH],[I_FIELD_INDEX@I_LEVEL_WIDTH],IC_FIELD_CODE_EMPTY);
|
||
```
|
||
|
||
### SETGATE
|
||
|
||
```
|
||
void SETGATE(INTEGER startX, INTEGER startY, INTEGER endX, INTEGER endY)
|
||
```
|
||
|
||
Defines a rectangular gate area, inclusive of both corners. The gate affects [`CANHEROGOTO`](#canherogoto), [`ISGATEEMPTY`](#isgateempty), and [`ISINGATE`](#isingate).
|
||
|
||
**Parameters**
|
||
|
||
- `startX`, `startY` — coordinates of the gate's top-left corner.
|
||
- `endX`, `endY` — coordinates of the gate's bottom-right corner.
|
||
|
||
**Examples**
|
||
|
||
```
|
||
MAT^SETGATE(3,1,16,4);
|
||
```
|
||
|
||
### SETROW
|
||
|
||
```
|
||
void SETROW(INTEGER row, INTEGER cellCode1, [INTEGER cellCode2, ...])
|
||
```
|
||
|
||
Sets field codes for all cells in the given row, starting from column `0`. Excess arguments (beyond the grid's width) are ignored.
|
||
|
||
**Parameters**
|
||
|
||
- `row` — row index.
|
||
- `cellCode1`, `cellCode2`, … — field codes for successive cells in the row.
|
||
|
||
**Examples**
|
||
|
||
```
|
||
MAT^SETROW(0,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6);
|
||
MAT^SETROW(1,6,6,6,2,2,2,2,2,2,2,2,2,2,2,2,2,2,6,6,6);
|
||
```
|
||
|
||
### TICK
|
||
|
||
```
|
||
void TICK()
|
||
```
|
||
|
||
Executes one physics tick. The grid is scanned bottom-to-top, left-to-right. For each stone the following cases are checked in order:
|
||
|
||
1. Is the cell directly below empty — schedule a downward move.
|
||
2. Is the cell below an enemy — schedule an explosion.
|
||
3. Are the diagonally adjacent cells (and the lateral neighbour) empty — schedule a diagonal slide.
|
||
|
||
Scheduled moves are appended to an internal queue and executed one-by-one by [`NEXT`](#next). Each entry records the source cell's X and Y, plus the operation code.
|
||
|
||
**Examples**
|
||
|
||
```
|
||
MAT^TICK();
|
||
```
|
||
|
||
## Signals
|
||
|
||
### ONINIT
|
||
|
||
Fired when the object is initialised.
|
||
|
||
### ONNEXT
|
||
|
||
Fired by [`NEXT`](#next) after a move that was not the last in the current queue. Signal arguments are the source cell's X (`$1`), Y (`$2`) and operation code (`$3`).
|
||
|
||
### ONLATEST
|
||
|
||
Fired by [`NEXT`](#next) after the last queued move. Arguments are identical to [`ONNEXT`](#onnext).
|
||
|
||
### ONSIGNAL
|
||
|
||
Fired when a signal arrives (see [Events and signals](../engine/events.md#onsignal)).
|