1060 lines
44 KiB
Markdown
1060 lines
44 KiB
Markdown
# Repixture Mobs API
|
||
|
||
NOTE: This API is EXPERIMENTAL and subject to change! Use at your own risk.
|
||
|
||
This is the API documentation for the Repixture Mobs API. This is the document
|
||
you want to read if you want to develop your own Repixture mobs.
|
||
|
||
## Core concepts
|
||
|
||
### Mobs
|
||
|
||
In this mod, a "mob" refers a non-player entity with added capabilities like a task queue. Mobs can be used to implement things like animals or monsters.
|
||
|
||
### Tasks, microtasks and task queues
|
||
|
||
The heart of this mod are tasks. A task is a single defined goal that a mob has to achieve in some manner. Every task can either succeed or fail. Tasks include high-level goals such as "find and eat food", "flee from the player", "kill the player", "roam randomly", etc.
|
||
|
||
Additionally, every task consists of a number of microtasks. These are intended for more low-level and simple activities such as "go to point XYZ", "jump", "attack in direction A". Microtasks are executed sequentially and can either succeed or fail. If a microtask fails, the task it is part of will end. If all microtasks in a task have finished, the task also ends.
|
||
|
||
To use tasks, they must be initialized by calling `rp_mobs.init_tasks` in `on_activate` and be handled (i.e. executed) every step by calling `rp_mobs.handle_tasks` in `on_step`.
|
||
|
||
Finally, task queues organize the execution of tasks. A task queue is a sequence of tasks that get executed in order. Tasks get automatically removed from the queue once finished. Task queues also optionally have a number of decider function which are called to determine which tasks to add next.
|
||
|
||
A mob can have any number of task queues active at a time. While tasks and microtasks are executed sequentially, task queues run in parallel.
|
||
|
||
Task queues, tasks and microtask form a tree-like structure, like so:
|
||
|
||
Task queue 1
|
||
|
|
||
`- Task 1
|
||
|
|
||
`- Microtask 1
|
||
`- Microtask 2
|
||
|
|
||
`- Task 2
|
||
`- Microtask 1
|
||
`- Microtask 2
|
||
|
||
So on the top level, you have task queues, which consist of tasks, which in turn consist of microtasks.
|
||
|
||
### Physics and movement
|
||
|
||
This mod doesn't handle physics. Just use Minetest’s built-in functions like `set_velocity` and `set_acceleration`.
|
||
|
||
There’s one exception: Gravity. This mod provides a default gravity vector at `rp_mobs.GRAVITY_VECTOR`.
|
||
|
||
To activate gravity for a mob, you use the `set_acceleration` microtask template (see the section about task templates).
|
||
|
||
### Registering a mob
|
||
You add (register) a mob via `rp_mobs.register_mob`. Mob definitions in this API are very low-level and similar to typical entity definitions in Minetest. You still have to provide a full entity definition via `entity_definition` including the callback functions like `on_activate` and `on_rightclick`.
|
||
|
||
You're supposed to use the Repixture Mob API functionality by inserting the various helper functions into the callbacks like `on_step` and `on_activate` where appropriate.
|
||
|
||
A template for a minimal mob looks like this:
|
||
|
||
rp_mobs.register_mob("MOBNAME", {
|
||
description = "MOB DESCRIPTION",
|
||
drops = { ADD_YOUR_DROPS_HERE },
|
||
entity_definition = {
|
||
initial_properties = {
|
||
-- Add the initial entity properties here
|
||
-- e.g. physical, hp_max, textures, etc.
|
||
},
|
||
on_activate = function(self, staticdata)
|
||
rp_mobs.init_mob(self)
|
||
rp_mobs.restore_state(self, staticdata)
|
||
rp_mobs.init_tasks(self)
|
||
end,
|
||
on_step = function(self, dtime, moveresult)
|
||
rp_mobs.handle_dying(self, dtime, moveresult)
|
||
rp_mobs.handle_tasks(self, dtime, moveresult)
|
||
end,
|
||
get_staticdata = rp_mobs.get_staticdata_default,
|
||
on_death = rp_mobs.on_death_default,
|
||
on_punch = rp_mobs.on_punch_default,
|
||
},
|
||
})
|
||
|
||
## Data structures
|
||
|
||
This section defines the data structures used in the functions below.
|
||
|
||
### Task
|
||
|
||
A task is just a table. It can have the following fields:
|
||
|
||
* `label`: Brief string explaining what the task does. Only for debug
|
||
* `task_queue`: Read-only reference to the task queue this task is part of or nil.
|
||
Will be set automatically when the task is added to a task queue.
|
||
|
||
Tasks can be created with `rp_mobs.create_task`.
|
||
|
||
The task's microtasks are stored internally. Use `rp_mobs.add_microtask_to_task`
|
||
to add a microtask to a task.
|
||
|
||
### Microtask
|
||
|
||
A microtask is a table with the following fields:
|
||
|
||
* `label`: Same as for tasks
|
||
* `on_step`: Called every step of the mob. Handle the microtask behavior here
|
||
* `is_finished`: Must returns `<finished>, <success>`
|
||
`<finished>` must be `true` once the microtask is finished.
|
||
`<success>` (optional) can be set when the microtask finished to either `true`
|
||
if it *successfully* finishes (default) or `false` if it finishes in failure
|
||
* `on_end`: Called when the microtask has ended. Useful for cleaning up state
|
||
* `on_start`: Called when the microtask has begun. Called just before `on_step`
|
||
* `singlestep`: If true, this microtask will run for only 1 step and automatically finishes (default: false)
|
||
* `statedata`: Table containing data that can be modified and read at runtime
|
||
* `task`: Read-only reference to the task this microtask is part of, or nil.
|
||
Will be set automatically when it is added to a task
|
||
|
||
Every microtask needs to have `on_step` and either `is_finished` or `singlestep = true`.
|
||
All other fields are optional. It is not allowed to add any fields not listed above.
|
||
|
||
`is_finished`, `on_end` and `on_start` have parameters `self, mob` with `self` being
|
||
a reference to the microtask table itself and `mob` being the mob object that is affected.
|
||
|
||
`on_step` has the parameters `self, mob, dtime, moveresult`, where `dtime` is the time in
|
||
seconds that have passed since it was last called, or 0 on the first call (like for the entity
|
||
`on_step` function), and `moveresult` is the `moveresult` of the entity `on_step`
|
||
function (only available if `physical=true` in the entity definition, otherwise it'll be `nil`).
|
||
|
||
The normal order of execution of the callbacks is as follows: `on_start` is called once
|
||
right after the microtask started. Then, in a a loop, first `is_finished` and then
|
||
`on_step` is called. If `is_finished` returned `false`, the microtask immediately
|
||
finishes (skipping the next `on_step`), causing `on_end` to get called.
|
||
|
||
In case of a `singlestep` microtask, the execution order is simply `on_start`,
|
||
`on_step` (once) and `on_end`. `is_finished` is not called.
|
||
|
||
When a microtask finishes, it either ends in success or failure. The default
|
||
is success. Setting the `success` return value of `is_finished` to `false` will
|
||
finish the microtask in failure. If a microtask finished in failure, the task
|
||
it contains will end, skipping the remaining microtasks. `singlestep` microtasks always end
|
||
in success.
|
||
|
||
The `statedata` field can be used to associate arbitrary data with the microtask in
|
||
order to preserve some state. You may read and write to it in functions
|
||
like `on_step`.
|
||
|
||
Microtasks can be created with `rp_mobs.create_microtask`.
|
||
|
||
## Subsystems
|
||
|
||
Subsystems implement core mob features. The task handling is also a subsystem.
|
||
|
||
Each subsystem is enabled by adding both a `handle_*` function in `on_step`
|
||
and an `init_*` function in `on_activate`. Some subsystems only require
|
||
one of these functions.
|
||
|
||
The `handle_*` function in `on_step` **must** be called on *every* step.
|
||
Failing to do so leads to undefined behavior.
|
||
|
||
For example, to enable the Tasks subsystem, call `rp_mobs.init_tasks` in `on_activate`
|
||
of the mob entity definition, and `rp_mobs.handle_tasks` in `on_step`. See the
|
||
function reference for details.
|
||
|
||
Some subsystems are mandatory (see overview below).
|
||
|
||
### Subsystem overview
|
||
|
||
This overview is a list of all subsystems and the required functions you need to call:
|
||
|
||
Subsystem | on_activate function | on_step function
|
||
------------+--------------------------+----------------------------
|
||
Core* | rp_mobs.init_mob | **
|
||
Tasks* | rp_mobs.init_tasks | rp_mobs.handle_tasks
|
||
Dying | ** | rp_mobs.handle_dying
|
||
Node damage | rp_mobs.init_node_damage | rp_mobs.handle_node_damage***
|
||
Fall damage | rp_mobs.init_fall_damage | rp_mobs.handle_fall_damage***
|
||
Breath | rp_mobs.init_breath | rp_mobs.handle_breath***
|
||
Breeding | ** | rp_mobs.handle_breeding
|
||
|
||
* = mandatory
|
||
** = no function required
|
||
*** = can be replaced with rp_mobs.handle_environment_damage
|
||
|
||
### Core subsystem
|
||
|
||
The core subsystem must always be added to the mob. It does initialization
|
||
work that is mandatory for all mobs. Add `rp_mobs.init_mob` to the beginning
|
||
of the `on_activate` function.
|
||
|
||
### Dying
|
||
|
||
An entity “dies” in Minetest when its HP reaches 0, which instantly removes it and
|
||
triggers the `on_death` function.
|
||
|
||
We do not like instant removal so this subsystem provides a simple graphical death
|
||
effect and delay. This flips over the mob and calls a special `dying_step`
|
||
function every step. This function is used by the mob to handle death physics stuff.
|
||
|
||
While dying, no tasks or microtasks are run.
|
||
The mob is still visible but all player interactions are disabled. After a short
|
||
delay, the mob disappears in a cloud of dust by setting the HP to 0,
|
||
causing `on_death` to be called.
|
||
|
||
To use this subsystem, add `rp_mobs.handle_dying` into `on_step`.
|
||
|
||
**IMPORTANT**: You also must follow a restriction: Never call `set_hp` to
|
||
damage the mob. Instead, punch the mob with the built-in `punch` function
|
||
or call `rp_mobs.damage`.
|
||
|
||
Internally, this subsystem works by storing a variable for the mob to hold
|
||
the dead/alive state. It can be queried with `rp_mobs.is_alive`.
|
||
When the mob has received fatal damage, the HP remains at 1 but the mob
|
||
counts as dead.
|
||
|
||
If this subsystem is not used, the mob will instantly disappear when the HP reaches 0.
|
||
But `on_death` is still called because this is built-in by Minetest.
|
||
|
||
### Node damage
|
||
|
||
Node damage enables the mob being vulnerable to nodes with the `damage_per_second` field.
|
||
If you want your mob to use this, add `rp_mobs.init_node_damage` to `on_activate`
|
||
and `rp_mobs.handle_node_damage` to `on_step`. Read the documentation of these
|
||
functions to learn more about how the node damage mechanic works.
|
||
|
||
Node damage can be temporarily disabled during the mob’s lifetime by setting the
|
||
entity field `_get_node_damage` to false.
|
||
|
||
### Fall damage
|
||
|
||
Fall damage hurts the mob when it hits the ground too hard.
|
||
The fall damage calculation works differently than for players (see
|
||
`rp_mobs.handle_fall_damage` for details.
|
||
|
||
To enable fall damage , add `rp_mobs.init_fall_damage` in `on_activate` and
|
||
`rp_mobs.handle_fall_damage` in `on_step`.
|
||
|
||
### Breath
|
||
|
||
The breath subsystem enables breath and a drowning mechanic. This makes mobs take
|
||
drowning damage when inside a particular node.
|
||
|
||
If you want your mob to use this, add `rp_mobs.init_breath` to `on_activate`
|
||
and `rp_mobs.handle_drowning` in `on_step`. Read the documentation of these
|
||
functions to learn more about the drowning mechanic in general.
|
||
|
||
The drowning status can also be changed during the mob’s lifetime in `on_step`
|
||
by manipulating the mob fields (see the mob field reference).
|
||
|
||
### Breeding
|
||
|
||
Breeding will make mobs mate and create offspring. To enable, call
|
||
`rp_mobs.handle_breeding` in `on_step`. You also need to add the tag
|
||
`"child_exists"` to the mob definition.
|
||
|
||
In particular, to breed, two adult mobs of the same type need to be “horny” and close
|
||
to each other. Then, a random mob of the pair gets pregnant and will soon
|
||
spawn a child mob. The child mob will grow to an adult after some time.
|
||
|
||
There are two ways to make a mob horny:
|
||
|
||
1. Call `rp_mobs.feed_tame_breed` in `on_rightclick` (i.e. player gives mob enough food)
|
||
2. Call `rp_mobs.make_horny` to instantly make the mob horny
|
||
|
||
Only adults should be horny.
|
||
|
||
|
||
## Mob field reference
|
||
|
||
Mob entities use a bunch of custom fields. You may read and edit them at runtime.
|
||
These fields are available:
|
||
|
||
### Status
|
||
|
||
* `_tamed`: `true` if mob is tame
|
||
* `_tame_level`: tame level. Starts at 0 and increases for any food given, used to trigger taming
|
||
* `_horny_level`: 'horny' level. Starts at 0 and increases for any food given to an adult, used to trigger horny mode
|
||
* `_child`: `true` if mob is a child
|
||
* `_horny`: `true` if mob is “horny”. If another horny mob is nearby, they will mate and spawn a child soon
|
||
* `_pregnant`: `true` if mob is pregnant and about to spawn a child
|
||
|
||
### Textures
|
||
|
||
NOTE: You must update these whenever you want to change a mob's texture. If the mob does not use a custom
|
||
child texture, `_textures_child` can be skipped.
|
||
|
||
* `_textures_adult`: Stores a copy of the current mob textures table for the mob in adult form
|
||
* `_textures_child`: Stores a copy of the current mob textures table for the mob in child form
|
||
|
||
### Damage
|
||
|
||
* `_get_node_damage`: `true` when mob can take damage from nodes (`damage_per_second`) (default: false)
|
||
* `_get_fall_damage`: `true` when mob can take fall damage (default: false)
|
||
* `_can_drown`: `true` when mob has breath and can drown in nodes with `drowning` attribute (default: false)
|
||
* `_drowning_point`: See `rp_mobs.init_breath`.
|
||
* `_node_damage_points`: See `rp_mobs_init_node_damage`.
|
||
* `_breath_max`: Maximum breath (ignored if `_can_drown` isn’t true)
|
||
* `_breath`: Current breath (ignored if `_can_drown` isn’t true)
|
||
|
||
Please note:
|
||
You *must* call `rp_mobs.init_node_damage` before you touch the `_get_node_damage` field.
|
||
You *must* call `rp_mobs.init_breath` before you touch the breath/drowning fields.
|
||
|
||
### Internal use
|
||
|
||
A bunch of fields are meant for internal use by `rp_mobs`. Do not change them. Reading them is fine.
|
||
|
||
* `_cmi_is_mob`: Always `true`. Indicates the entity is a mob
|
||
* `_child_grow_timer`: time the mob has been a child (seconds)
|
||
* `_horny_timer`: time the mob has been horny (seconds)
|
||
* `_breed_check_timer`: timer for the breed check (seconds)
|
||
* `_pregnant_timer`: time the mob has been pregnant (seconds)
|
||
* `_last_feeder`: Name of the last player who fed the mob
|
||
* `_dying`: Is `true` when mob is currently dying and about to be removed
|
||
* `_dying_timer`: time the mob has been in the dying state (seconds)
|
||
|
||
## Function reference
|
||
|
||
### Registrations
|
||
|
||
#### `rp_mobs.register_mob(mobname, def)`
|
||
|
||
Register a mob with the entity identifier `mobname` and definition `def`.
|
||
The mob definition will be stored under `rp_mobs.registered_mobs[mobname]`.
|
||
The field `_cmi_is_mob=true` will be set automatically for all mobs and can be used to check whether any given entity is a mob.
|
||
|
||
`def` is a definition table with the following optional fields:
|
||
|
||
* `description`: Mob name used for display purposes (can be translated)
|
||
* `entity_definition`: Entity definition table. Put everything in here that is also used
|
||
for normal entities (`on_step`, `on_activate`, `initial_properties`, etc.).
|
||
* It may also contain this custom function:
|
||
* `_on_capture(self, capturer)`: Called when a mob capture is attempted by capturer (a player).
|
||
Triggered by `rp_mobs.call_on_capture`
|
||
* `drops`: Items to drop (see 'Drop table' below) on death when mob dies as adult (default: empty table)
|
||
* `child_drops`: Items to drop when mob dies as child (default: empty table)
|
||
* `drop_func(self)`: (optional) Called when mob is dropping its death drop items. Must return table of items to drop.
|
||
These items are dropped on top of the items in `drops` and `child_drops`.
|
||
This function **must not** manipulate the mob in any way.
|
||
* `default_sounds`: Table of default sound names to play automatically on built-in events. Sounds will be played by `rp_mobs.default_mob_sound`
|
||
* `death`: When mob dies
|
||
* `damage`: When mob takes non-fatal damage
|
||
* `punch_no_damage`: When mob was punched but took no damage
|
||
* `eat`: When mob eats something (this has a default sound)
|
||
* `call`: Occasional mob call (only played manually)
|
||
* `horny`: When mob becomes horny
|
||
* `give_birth`: When mob gives birth to a child
|
||
* `front_body_point`: A point of the front side of the mob, specified as offset vector from the mob position.
|
||
Used to "see" forwards to detect dangerous land (cliffs, damaging blocks, etc.)
|
||
Should be on the front face of the mob model and roughly in the center of that side.
|
||
* `path_check_point`: A point that is used to compare the position to the path goal position,
|
||
specified as offset vector from the mob position. This point is used to check whether a path point
|
||
has been 'reached' for the `follow_path*` microtask templates.
|
||
* `dead_y_offset`: Y offset of collisionbox when mob is in 'dying' state. Set this to
|
||
a number so that the mob lies on top of the ground (it should neither
|
||
float nor be inside the ground)
|
||
* `textures_child`: If set, this will be the mob texture for the mob as a child. Same syntax as `textures`
|
||
of the entity definition. Adult mobs will use `textures`
|
||
* `animations`: Table of available mob animations
|
||
* The keys are string identifies for each animation, like `"walk"`
|
||
* The values are tables with the following fields:
|
||
* `frame_range`: Same as `frame_range` in `object:set_animation`
|
||
* `default_frame_speed`: Default `frame_speed` (from `object:set_animation`) when this animation is played
|
||
* Built-in animations are:
|
||
* `"idle"`: Played when mob has nothing to do (empty task queue)
|
||
* `"dead_static"`: Played when mob is dead (there normally should be no animation, just a static frame)
|
||
* `tags`: Table of tags.
|
||
* Tags are arbitrary strings used to logically categorize the mob.
|
||
* The table keys are tag names and value for each key must always be 1.
|
||
* You can check if a mob has a tag with `rp_mobs.has_tag` or `rp_mobs.mobdef_has_tag`.
|
||
* Built-in tag names:
|
||
* `"animal"`: This mob is an animal. Some items and achievements check for this tag
|
||
* `"peaceful"`: Mob is considered 'peaceful' towards players if unprovoked. It normally leaves
|
||
the player alone. Peaceful mobs may still turn hostile when provoked.
|
||
Mobs that start hostile towards the player do not count as peaceful.
|
||
If the setting `spawn_peaceful_only` is enabled, only mobs with this tag can spawn.
|
||
* `"child_exists"`: Mob has a functional child version. This tag is only
|
||
for informing other mobs; it does not have an effect in the `rp_mobs` code
|
||
* Example: `tags = { animal = 1, peaceful = 1, exploder = 1 }`
|
||
* A peaceful animal, plus a custom `"exploder"` tag.
|
||
|
||
##### Drop table
|
||
|
||
For the arguments `drops` and `child_drops`, two types of values are supported:
|
||
|
||
1. List of itemstrings: Will drop every item in this list
|
||
2. List of drop probabilities: Will drop items by a given chance
|
||
* `name`: Technical itemname
|
||
* `chance`: Chance given in 1/`chance` for this item to drop
|
||
* `min`: Minimum item count when it drops
|
||
* `max`: Maximum item count when it drops
|
||
|
||
#### `rp_mobs.register_mob_item(mobname, invimg, desc, on_create_capture_item)`
|
||
|
||
Registers an item representing a mob. It can be used by players to spawn
|
||
the mob by placing it. This item is also used when a mob is captured.
|
||
|
||
The mob item may contain metadata to store the mob's HP and internal state
|
||
of the mob.
|
||
|
||
* `mobname`: Mob identifier
|
||
* `invimg`: Inventory image texture
|
||
* `desc`: Description for inventory
|
||
* `on_create_capture_item`: (optional) Function is called with arguments
|
||
`mob, itemstack` when mob has been captured and is becoming an itemstack.
|
||
You can use this function to modify the itemstack's metadata, e.g. to
|
||
use a item image override if the mob can have different appearances.
|
||
**Must** return an itemstack for the itemstack that is *actually* created.
|
||
|
||
#### `rp_mobs.register_capture_tool(itemname, definition)`
|
||
|
||
Registers an *existing* tool as a capture tool.
|
||
|
||
* `itemname`: Item name of the item that captures
|
||
* `definition`: A table with these fields:
|
||
* `uses`: Number of uses before the tool breaks. 0 = unlimited
|
||
* `sound`: (optional) Name of sound that plays when “swinging” the tool
|
||
* `sound_gain`: (optional) Gain of that sound (as in `SimpleSoundSpec`)
|
||
* `sound_max_hear_distance`: (optional) `max_hear_distance` of that sound (as in `SimpleSoundSpec`)
|
||
|
||
This mod comes with a lasso (`rp_mobs:lasso`) and a net (`rp_mobs:net`) by default.
|
||
|
||
|
||
|
||
### Default entity handlers
|
||
|
||
These functions should be set as the callback functions of the mob entity.
|
||
The code will usually look like this:
|
||
|
||
```
|
||
get_staticdata = rp_mobs.get_staticdata_default, -- required
|
||
on_death = rp_mobs.on_death_default, -- optional, custom handler recommended
|
||
on_punch = rp_mobs.on_punch_default, -- optional
|
||
```
|
||
|
||
#### `rp_mobs.get_staticdata_default(mob)`
|
||
|
||
The default handler for `get_staticdata` of the mob's entity definition.
|
||
This will handle the staticdata of the mob for you. All the mob's internal
|
||
information will be stored including the `_custom_state`.
|
||
`_temp_custom_state` will be discarded.
|
||
|
||
This function *must* be set explicitly for every mob.
|
||
|
||
Set `get_staticdata = rp_mobs.get_staticdata_default` to use this.
|
||
|
||
#### `rp_mobs.on_death_default(mob, killer)`
|
||
|
||
The default handler for `on_death` of the mob's entity definition.
|
||
It should be set explicitly for every mob, unless you want to have a custom death handling.
|
||
Currently, the default death handler just drops the mob death items.
|
||
|
||
Set `on_death = rp_mobs.on_death_default` to use the default death behavior.
|
||
|
||
Remember that `on_death` is a built-in Minetest event, so it is triggered even
|
||
if the mob doesn't use the 'Dying' subsystem.
|
||
|
||
#### `rp_mobs.on_punch_default(mob, puncher, time_from_last_punch, tool_capabilities, dir, damage)`
|
||
|
||
Default `on_punch` handler of mob. Set this function to `on_punch`. The arguments are the same as for `on_punch`.
|
||
|
||
This will play a the `damage` sound if the mob took damage, otherwise, `hit_no_damage` is played (if the sound exists).
|
||
|
||
|
||
|
||
### `on_activate` functions
|
||
|
||
These are functions to be used in the `on_activate` handler to initialize certain subsystems, like tasks.
|
||
|
||
Calling `rp_mobs.init_mob`, `rp_mobs.restore_state` and `rp_mobs.init_tasks` in `on_activate` are required, but everything else is optional depending on your needs.
|
||
|
||
#### `rp_mobs.init_mob(mob)`
|
||
|
||
This initializes the mob and does initialization work in order to do things that
|
||
are required for all mobs.
|
||
|
||
This function **must** be called in `on_activate` before any other mob-related function.
|
||
|
||
#### `rp_mobs.restore_state(mob, staticdata)`
|
||
|
||
This will restore the mob's state data from the given `staticdata` in `on_activate`.
|
||
|
||
This *must* be called in `on_activate` before any other mob-related function except
|
||
`rp_mobs.init_mob`.
|
||
|
||
#### `rp_mobs.init_tasks(mob)`
|
||
|
||
Initialize the task and microtask queues for the mob.
|
||
This is supposed to go into `on_activate` of the entity definition.
|
||
|
||
This *must* be called in `on_activate` before any other mob-related function except
|
||
`rp_mobs.init_mob` and `rp_mobs.restore_state`.
|
||
|
||
#### `rp_mobs.init_breath(mob, can_drown, def)`
|
||
|
||
Initializes the breath and drowning mechanic for the given mob.
|
||
If you want the mob to have this, put this into `on_activate`.
|
||
|
||
If this is the first time the function is called, drowning and
|
||
associated entity fields will be initialized. On subsequent calls,
|
||
this function does nothing because the fields are already
|
||
initialized.
|
||
|
||
* `self`: The mob
|
||
* `can_drown`: If `true`, drowning is possible, otherwise, it is not.
|
||
* `def`: A table with the following fields:
|
||
* `breath_max`: Maximum (and initial) breath points. Positive number
|
||
* `drowning_point`: Optional position offset vector that will be checked when doing
|
||
the drowning check. It’s an offset from the mob position (`get_pos()`). If the
|
||
node at the drowning point is a drowning node, the mob can drown.
|
||
Default: no offset
|
||
|
||
#### `rp_mobs.init_node_damage(mob, get_node_damage, def)`
|
||
|
||
Initializes the node damage mechanic for the given mob,
|
||
activating damage from nodes with `damage_per_second`.
|
||
If you want the mob to have this, put this into `on_activate`.
|
||
|
||
Node damage is dealt by checking one or more "node damage
|
||
points" for a node with node damage. By default, the mob
|
||
position (`get_pos()`) is checked.
|
||
|
||
Negative damage heals the mob.
|
||
|
||
If this is the first time the function is called, the
|
||
associated entity fields will be initialized. On subsequent
|
||
calls, this function does nothing because the fields are already
|
||
initialized.
|
||
|
||
Parameters:
|
||
|
||
* `mob`: The mob
|
||
* `get_node_damage`: If `true`, mob will receive damage from nodes
|
||
* `def`: A table with the following field:
|
||
* `node_damage_points`: Optional list of position offset vectors that will be checked
|
||
when doing the node damage check. Each vector is an offset from the
|
||
mob position (`get_pos()`). If any of the checked positions has a
|
||
node with a non-zero `damage_per_second`, then the node with the highest
|
||
such value will be the damage dealt.
|
||
Default: no offset
|
||
|
||
Recommendations:
|
||
|
||
It is strongly recommended you always explicitly define the node damage point(s).
|
||
|
||
For small mobs, one node damage point is sufficient.
|
||
For large mobs (that usually occupy a space much larger than 1 node),
|
||
multiple points should be used.
|
||
Each damage point should be inside the mob's "body" and be roughly centered.
|
||
Use as few points as possible for performance reasons, and don't put them
|
||
too close to each other.
|
||
|
||
#### `rp_mobs.init_fall_damage(mob, get_fall_damage)`
|
||
|
||
Initializes the fall damage for the given mob,
|
||
If you want the mob to have this, put this into `on_activate`.
|
||
|
||
If this is the first time the function is called, the
|
||
associated entity fields will be initialized. On subsequent
|
||
calls, this function does nothing because the fields are already
|
||
initialized.
|
||
|
||
Parameters:
|
||
|
||
* `self`: The mob
|
||
* `get_fall_damage`: If `true`, mob will receive fall damage
|
||
|
||
|
||
|
||
### Subsystems: `on_step` handlers
|
||
|
||
These are functions you need to call in the `on_step` callback function of the mob entity in order for a subsystem to work properly.
|
||
Each of these functions assumes the corresponding `rp_mobs.init_*` function (if any) has been called before.
|
||
|
||
#### `rp_mobs.handle_tasks(mob, dtime, moveresult)`
|
||
|
||
Handle the task queues, tasks, microtasks of the mob for a single step. Required for the task system to work.
|
||
This is supposed to go into `on_step` of the entity definition. It must be called every step.
|
||
|
||
`dtime` and `moveresult` must be passed from the arguments of the same name of the entity’s `on_step`.
|
||
|
||
#### `rp_mobs.handle_dying(self, dtime, moveresult, dying_step)`
|
||
|
||
Handles the dying state of the mob if the mob has been killed. If the internal mob state
|
||
`_dying` is true, the dying effect is applied and the mob gets removed after a short delay,
|
||
triggering the `on_death` callback. It is recommended to put this function before
|
||
any other `rp_mobs` subsystem calls.
|
||
|
||
See the section about the “Dying” subsystem for details.
|
||
|
||
Parameters:
|
||
|
||
* `self`: Reference to mob object
|
||
* `dtime`: Same as for the entity’s `on_step`
|
||
* `moveresult`: Same as for the entity’s `on_step`
|
||
* `dying_step`: Optional function with signature `self, dtime, moveresult`
|
||
that is called every step while the mob is in *dying* state
|
||
|
||
#### `rp_mobs.handle_node_damage(mob, dtime)`
|
||
|
||
Handles node damage for the mob if the entity
|
||
field `_get_node_damage` is `true`. Node damage is taken
|
||
when the mob is inside any node with a non-zero `damage_per_second`.
|
||
Negative damage heals the mob.
|
||
|
||
See `rp_mobs.init_node_damage` for details on how the mod
|
||
determines when the mob is inside such a node.
|
||
|
||
Must be called in the `on_step` function in every step.
|
||
`dtime` is the `dtime` argument of `on_step`.
|
||
|
||
#### `rp_mobs.handle_fall_damage(mob, dtime, moveresult)`
|
||
|
||
Handles fall damage for the mob if the entity field
|
||
`_get_fall_damage` is `true`.
|
||
|
||
Fall damage is calculated differently than for
|
||
players and there is no guarantee the calculation
|
||
algorithm will be forwards-compatible. It increases
|
||
linearly with the fall height. Fall damage is further-
|
||
more modified by the `add_fall_damage_percent` group
|
||
if falling on a node. Adding the armor group
|
||
`add_fall_damage_percent` will also modify the
|
||
fall damage the mob will receive (like for players,
|
||
see `lua_api.md`).
|
||
|
||
This function must be called in the `on_step` function
|
||
in every step. `dtime` and `moveresult` are the same as
|
||
in `on_step`.
|
||
|
||
#### `rp_mobs.handle_drowning(mob, dtime)`
|
||
|
||
Handles breath and drowning damage for the mob
|
||
if the entity field `_can_drown` is `true`.
|
||
|
||
Mob drowning is meant to closely reflect player drowning.
|
||
|
||
This function requires that `rp_mobs.init_breath` has
|
||
been called in `on_activate` before.
|
||
|
||
Technically, this function checks if the mob’s drowning
|
||
point is inside a node with a non-zero non-nil `drowning`
|
||
field. If this is the case, the internal `_breath` will
|
||
be reduced in a regular interval. If `_breath` reaches zero,
|
||
the mob will take damage equal to the node’s `drowning` value.
|
||
If the mob is in a non-drowning node, it will regain
|
||
breath up to `_breath_max`.
|
||
In `"ignore"` nodes, breath will not be changed.
|
||
|
||
By default, the mob position will be checked to determine
|
||
which node is checked for the `drowning` field. If
|
||
`_drowning_point` is set, is must be a vector added to
|
||
the mob position to check a different position. This
|
||
is usually where the mob’s “head” is. The drowning point
|
||
must be inside the collisionbox, otherwise the mob
|
||
might regain breath when touching a wall.
|
||
The drowning point will automatically be rotated with the
|
||
mob’s yaw.
|
||
|
||
Must be called in the `on_step` function in every step.
|
||
`dtime` is the `dtime` argument of `on_step`.
|
||
|
||
#### `rp_mobs.handle_environment_damage(mob, dtime, moveresult)`
|
||
|
||
Handle all environment damages. This is is the same as calling:
|
||
|
||
* `rp_mobs.handle_fall_damage`
|
||
* `rp_mobs.handle_node_damage`
|
||
* `rp_mobs.handle_drowning`
|
||
|
||
Must be called in `on_step` every step.
|
||
`mob` is the mob. The `dtime` and `moveresult` arguments are the same as for `on_step`.
|
||
|
||
|
||
|
||
### Task functions
|
||
|
||
This section contains the functions to create tasks, microtasks and task queues and to add them to the mob.
|
||
|
||
See also `rp_mobs.init_tasks` and `rp_mobs.handle_tasks`.
|
||
|
||
#### `rp_mobs.create_task_queue(empty_decider, step_decider, start_decider)`
|
||
|
||
Create a task queue object and returns it. The arguments are
|
||
optional decider functions.
|
||
|
||
In these decider functions you can update the task queue by adding new
|
||
tasks to it. Avoid complex and slow algorithms here!
|
||
|
||
* `empty_decider(task_queue, mob)`: called when the task queue is empty
|
||
* `step_decider(task_queue, mob, dtime)`: called at every server step
|
||
* `start_decider(task_queue, mob)`: called right after the task queue starts
|
||
|
||
The function arguments are:
|
||
|
||
* `task_queue`: Reference to task queue on which the decider is run on.
|
||
You can modify it at will.
|
||
* `mob`: Reference to mob object
|
||
* `dtime`: Time in seconds the last time the step decider was called (from `on_step`)
|
||
|
||
If decider argument is nil, nothing will be done for that event.
|
||
|
||
#### `rp_mobs.create_task(def)`
|
||
|
||
Create a task according to the specified `def` table. See the data structure above for the possible table fields.
|
||
|
||
Returns the task.
|
||
|
||
#### `rp_mobs.create_microtask(def)`
|
||
|
||
Create a microtask according to the specified `def` table. See the data structure above for the possible table fields.
|
||
The `statedata` field will always be initialized as an empty table.
|
||
|
||
Returns the microtask.
|
||
|
||
Note this only creates it in memory. To actually execute it, you have to add
|
||
it to a task with `rp_mobs.add_microtask_to_task` and then execute
|
||
the task.
|
||
|
||
#### `rp_mobs.add_task_queue(mob, task_queue)`
|
||
|
||
Add a task queue to the given mob.
|
||
|
||
#### `rp_mobs.add_task_to_task_queue(task, task_queue)`
|
||
|
||
Add a task `task` to the given task queue object. Will also initialize the `task_queue` field of `task`.
|
||
|
||
#### `rp_mobs.add_microtask_to_task(mob, microtask, task)`
|
||
|
||
Add the microtask `microtask` to the specified `task`. Will also initialize the `task` field of `microtask`.
|
||
|
||
#### `rp_mobs.end_current_task_in_task_queue(mob, task_queue)`
|
||
|
||
Ends the currently active task in the given `task_queue` of `mob`.
|
||
If the task queue is empty, nothing happens.
|
||
|
||
#### `rp_mobs.clear_task_queue(mob, task_queue)`
|
||
|
||
Remove all tasks in the given `task_queue`.
|
||
|
||
|
||
|
||
### Task template functions
|
||
|
||
This mod comes with a number of template functions for common microtasks like walking. See `API_TEMPLATES.md` for a reference.
|
||
|
||
|
||
|
||
### Breeding functions
|
||
|
||
#### `rp_mobs.feed_tame_breed(mob, feeder, allowed_foods, food_till_tamed, food_till_horny, add_child_grow_timer, effect, eat_sound)`
|
||
|
||
Requires the Breeding subsystem.
|
||
|
||
Let the player `feeder` feed the `mob` with their wielded item and optionally cause the mob to become tame and become horny.
|
||
Should be called in `on_rightclick`.
|
||
|
||
* `mob`: The mob that is fed
|
||
* `feeder`: Player who feeds the mob
|
||
* `allowed_foods`: List of allowed food items, where each entry must be a table with these fields:
|
||
* `name`: item name of food item
|
||
* `points` optional food points to add to mob when eaten, defaults to `_rp_hunger_food`
|
||
of item definition, and if that one is nil, too, defaults to 1
|
||
* `food_till_tamed`: How many food points the mob needs until it is tamed (if nil, can't be tamed by food)
|
||
* `food_till_horny`: How many food points the adult mob needs until it becomes horny (if nil, can't be made horny by food)
|
||
* `add_child_growth_timer`: (optional) If mob is a child, by how many seconds the child growth timer is increased (default: `20`)
|
||
* `effect`: (optional) `true` to show particle effects, `false` otherwise (default: `true`)
|
||
* `eat_sound`: (optional) Name of sound to play (default: `"mobs_eat"`)
|
||
|
||
#### `rp_mobs.make_horny(mob, force)`
|
||
|
||
Make mob horny, if possible.
|
||
|
||
#### `rp_mobs.make_unhorny(mob)`
|
||
|
||
Disable mob being horny.
|
||
|
||
|
||
|
||
### Children functions
|
||
|
||
These function handle the child/adult status of the mob.
|
||
|
||
#### `rp_mobs.turn_into_adult(mob)`
|
||
|
||
Turns the mob into an adult.
|
||
|
||
#### `rp_mobs.turn_into_child(mob)`
|
||
|
||
Turns the mob into a child.
|
||
IMPORTANT: You *must* check whether the mob has the `"child_exists"` tag before calling this.
|
||
Don't call this function if the mob does not have this tag.
|
||
|
||
#### `rp_mobs.advance_child_growth(mob, dtime)`
|
||
|
||
Advance the child growth timer of the given mob by dtime (in seconds) and turn it into an adult once the time has passed.
|
||
Should be added into the `on_step` function of the mob if you want children to grow up. If children must not grow up automatically, don't add this function.
|
||
|
||
|
||
|
||
### Capturing functions
|
||
|
||
#### `rp_mobs.attempt_capture = function(mob, capturer, capture_chances, force_take, replace_with)`
|
||
|
||
Attempt to capture mob by capturer (a player). This requires a mob to have a mob available as
|
||
an item (see `rp_mobs.register_mob_items`), unless `replace_with` is set.
|
||
|
||
It is recommended to put this function in the `_on_capture` field of the mob’s entity definition.
|
||
|
||
A mob capture may or may not succeed. A mob capture succeeds if all of these are true:
|
||
|
||
1. The wielded item is in `capture_chances`
|
||
2. The random capture chance succeeds
|
||
3. The mob is tamed _or_ `force_take` is `true`
|
||
4. The mob is not a child
|
||
|
||
If successful, `capturer` will get the mob in item form and it will wear out the wielded tool
|
||
(exception: no wear in Creative Mode). `capturer` might receive a message. If the mob
|
||
was horny, the mob will stop being horny.
|
||
|
||
The created mob item stores the internal mob state and HP so it will be restored
|
||
when the mob is being placed again.
|
||
|
||
Parameters:
|
||
|
||
* `mob`: The mob to capture
|
||
* `capturer`: Player trying to capture
|
||
* `capture_changes`: Table defining which items can capture and their chance to capture:
|
||
* key: itemname, e.g. `"rp_mobs:net"`
|
||
* value: capture chance, specified as `1/value`. e.g. value 5 is a 1/5 chance.
|
||
* `force_take`: (optional) If `true`, can capture non-tamed mob
|
||
* `replace_with`: (optional) Give this item instead of the registered mob item.
|
||
If `nil` (default), gives the registered mob item.
|
||
|
||
#### `rp_mobs.call_on_capture(mob, capture)`
|
||
|
||
Handle the mob’s capturing logic by calling the `_on_capture` function of the mob’s entity definition. This function *must* exist.
|
||
|
||
It is recommended to put this into `on_rightclick`, if you want this mob to be capturable.
|
||
|
||
|
||
|
||
### Animation functions
|
||
|
||
#### `rp_mobs.set_animation(mob, animation_name, animation_speed)`
|
||
|
||
Set the animation for the given mob. The animation name is set in the mob definition in `_animations`. The name *must* exist in this table.
|
||
|
||
* `mob`: Mob object
|
||
* `animation_name`: Name of the animation to play, as specified in mob definition
|
||
* `animation_speed`: (optional): Override the default frame speed of the animation
|
||
|
||
This function handles the animation internals for you. If you call this function with the same animation name and speed twice in a row, nothing happens; no unnecessary network traffic is generated. If you call the function twice in row with the same animation name but a different frame speed, only the animation speed is updated the animation does not restart. In all other cases, the animation is set and started from the beginning.
|
||
|
||
|
||
|
||
### Sound functions
|
||
|
||
#### `rp_mobs.mob_sound(mob, sound, keep_pitch)`
|
||
|
||
Plays a sound for the given mob. The pitch will be slightly randomized. Child mobs have a 50% higher pitch.
|
||
|
||
* `mob`: Mob to play sound for
|
||
* `sound`: A `SimpleSoundSpec`
|
||
* `keep_pitch`: If `true`, pitch will not be randomized and not be affected by child status
|
||
|
||
#### `rp_mobs.default_mob_sound(mob, default_sound, keep_pitch)`
|
||
|
||
Plays a default mob sound for the given mob. Default sounds are specified in `rp_mobs.register_mob`. The pitch will be slightly randomized. Child mobs have a 50% higher pitch.
|
||
|
||
* `mob`: Mob to play sound for
|
||
* `default_sound`: Name of a default mob sound, like `"damage"`
|
||
* `keep_pitch`: If `true`, pitch will not be randomized and not be affected by child status
|
||
|
||
#### `rp_mobs.default_hurt_sound(mob, keep_pitch)`
|
||
|
||
Make a mob play its “hurt” sound. The pitch will be slightly randomized. Child mobs have a 50% higher pitch.
|
||
|
||
* `mob`: Mob to play sound for
|
||
* `keep_pitch`: If `true`, pitch will not be randomized and not be affected by child status
|
||
|
||
|
||
|
||
### Event functions
|
||
|
||
#### `rp_mobs.register_on_die(callback)`
|
||
|
||
Registers a function `callback(mob, killer)` where `mob` is a mob reference and `killer`
|
||
is an object reference to the mob killer (or nil if there's no killer).
|
||
|
||
The function `callback` will be called when a mob has entered the 'dying' state, i.e.
|
||
lying on the side but not disappeared yet.
|
||
|
||
This is useful to trigger kill-related achievements but it could be used
|
||
for other reasons.
|
||
|
||
NOTE: This function differs from `on_death` in the entity definition. This callback
|
||
triggers when the mob enters the 'dying' state, while `on_death` triggers when
|
||
the entity is being *actually* removed.
|
||
|
||
IMPORTANT: Due to the implementation details, `on_death` will *not* provide
|
||
the correct `killer` argument.
|
||
|
||
|
||
|
||
### Utility functions
|
||
|
||
#### `rp_mobs.is_alive(mob)`
|
||
|
||
Returns `true` if the given mob is alive, false otherwise. `mob` *must* be a mob.
|
||
|
||
#### `rp_mobs.heal(mob, heal)`
|
||
|
||
Adds `heal` HP to mob. `heal` must not be negative.
|
||
|
||
#### `rp_mobs.damage(mob, damage, no_sound, damager)`
|
||
|
||
Removes `damage` HP from mob, optionally playing the mob's `"damage"` sound. `damage` must not be negative. If the mob HP reaches 0, the mob dies.
|
||
`damager` is an optional object that damaged the mob (used for Hunter achievement).
|
||
|
||
If `no_sound` is true, then no damage sound will be played.
|
||
|
||
It is recommended to damage a mob only with this function instead of calling `mob:set_hp` directly in order to ensure consistency across mobs.
|
||
|
||
#### `rp_mobs.die(mob, killer)`
|
||
|
||
Kill the mob by putting it into the 'dying' state.
|
||
`killer` is an optional object that killed the mob (used for Hunter achievement).
|
||
|
||
See the section about the “Dying” subsystem for details.
|
||
|
||
#### `rp_mobs.drop_death_items(mob, pos)`
|
||
|
||
Make mob `mob` drop its death items at `pos`, as if it had died.
|
||
|
||
This does not kill the mob.
|
||
|
||
#### `rp_mobs.spawn_mob_drop(pos, item)`
|
||
|
||
Spawns an mob drop at `pos`. A mob drop is an item stack “dropped”
|
||
by a mob, usually on death, but it may also be used on other events.
|
||
|
||
`item` is an itemstring or `ItemStack`
|
||
|
||
The difference from `minetest.add_item` is that it adds some random velocity
|
||
so this is the recommended function to make a mob drop something.
|
||
|
||
#### `rp_mobs.has_tag(mob, tag_name)`
|
||
|
||
Returns true if mob has the given `tag_name` or false if not.
|
||
|
||
See `rp_mobs.register_mob` for for info about tags.
|
||
|
||
#### `rp_mobs.mobdef_has_tag(mob_name, tag_name)
|
||
|
||
Return true if the mob definition for the mob with the given `mob_name` exists
|
||
and has a tag with the given `tag_name`. Returns false otherwise.
|
||
|
||
See `rp_mobs.register_mob` for for info about tags.
|
||
|
||
#### `rp_mobs.scan_environment(mob, dtime, y_offset)`
|
||
|
||
Call this function for a mob at every step in an `on_step` handler
|
||
to regularly check for nodes nearby the mob, so that other functions
|
||
don't have to call `minetest.get_node` over and over again.
|
||
|
||
After this function was called, the mob will have the following fields added which you can read from:
|
||
|
||
* `_env_node`: Node table of the node at the mob position.
|
||
* `_env_node_floor`: Same as `_env_node`, but for exactly 1 node below the mob position
|
||
|
||
These fields should only be read from, not written to.
|
||
|
||
Parameters:
|
||
|
||
* `mob`: Mob object
|
||
* `dtime`: `dtime` from `on_step`
|
||
* `y_offset`: Y offset of roughly the "center point" of the mob,
|
||
relative to the mob position (`get_pos()`)
|
||
|
||
#### `rp_mobs.drag(mob, dtime, drag, drag_axes)`
|
||
|
||
Slow mob down for the specified drag vector at the specified drag axes.
|
||
The drag vector specifies on each axis how much the mob slows down.
|
||
|
||
This will call `set_velocity` directly.
|
||
|
||
Parameters:
|
||
|
||
* `mob`: Mob object
|
||
* `dtime`: `dtime` from `on_step`
|
||
* `drag`: Drag vector. Higher number = faster slowdown.
|
||
* `drag_axes`: List of axes to which apply drag for (`"x"`, `"y"`, `"z"`).
|
||
Other axes will be ignored.
|
||
|
||
## Appendix
|
||
|
||
### Glossary
|
||
|
||
* Mob: Non-player entity with added capabilities
|
||
* Task queue: Sequence of tasks a mob will execute in order; can run in parallel
|
||
* Task: A sequence of microtasks a mob will execute in order; can’t run in parallel
|
||
* Microtask: A simple function a mob will execute every step until a goal condition is met
|
||
* Decider: A function that is called in order to determine what to do next in a task queue
|
||
* Subsystem: An implementation of core mob features; must be enabled with special functions
|
||
* Tag: A simple property that can be assigned to mobs. Used to categorize mobs
|
||
* Breed: Bringing two mobs of the same kind together with food to create a child mob
|
||
* Tame: A mob eventually becomes tame when it has been fed. Tame mobs behave differently
|
||
|
||
|
||
### Why yet another Mob API?
|
||
|
||
If you know the Minetest community, you might have noticed there are a lot of Mob APIs around.
|
||
So why introduce another one?
|
||
|
||
The reason is that I (Wuzzy) was dissatisfied with the current mod situation.
|
||
|
||
The mob mods that existed went into one of two extremes:
|
||
|
||
The one extreme is Mobs Redo, hardcoding a lot of features into the core mod, but
|
||
this created unmaintainable spaghetti code, made it hard to actually customize the mobs apart
|
||
from changing some parameters. This usually was also quite wasteful since mobs executed
|
||
code they didn't need. Repixture previously used an ancient fork for Mobs Redo.
|
||
But the upside of Mobs Redo was that it was very easy to add lots of new mobs, but they
|
||
couldn't differ that much.
|
||
|
||
The other extreme is mobkit. This mod basically wanted to do away with all the hardcoded
|
||
behavior and instead give the developer more freedom.
|
||
This approach sounded good at first but the problem was that mobkit offered so
|
||
little in useful features that you basically still had to start from (almost) zero
|
||
to re-implement common features that the mod should have already given you.
|
||
This defeats the point of an API.
|
||
Other mods that looked promising disappointed me because they are very poorly documented,
|
||
making them useless for productive development.
|
||
|
||
Since none of the available options were satisfying, I've finally decided to create
|
||
my own mobs API, which should allow me to add a diverse set of mobs relatively
|
||
easy.
|
||
|
||
I want to have a middle ground between offering many useful built-in features
|
||
and flexibility. Performance and documentation are also very important.
|
||
|
||
#### How this mod aims to reach these goals
|
||
|
||
The core of this mod is the 'task queue' system. This system serves two
|
||
purposes: First, to allow mobs to execute tasks in a certain order, one
|
||
after another. This idea was directly inspired by 'The Sims'. In this
|
||
game, the sims (the characters you control) also have queue of tasks.
|
||
This should allow to add complex tasks that require multiple steps.
|
||
|
||
Second, and more importantly, mobs can have multiple task queues at
|
||
once, each of which will be run in parallel. This was loosely
|
||
inspired by multitasking of operating systems.
|
||
|
||
Parallelization helps greatly improve performance and flexibility,
|
||
and it simplifies many complex things. For example, a mob can now
|
||
continuously scan its surroundings for enemies while still moving
|
||
around. The code for both tasks can be cleanly separated.
|
||
|
||
Another core concept in this mod is modularization. While this mod
|
||
offers built-in high-level features such as drowning, fall damage, breeding
|
||
and more, almost everything is optional. Features need to be explicitly
|
||
enabled for a mob before they are used. So mobs can only use the
|
||
features they need, which is again good for performance and
|
||
flexibility.
|