Skip to content

Usage Instructions

sorcerykid edited this page May 18, 2020 · 1 revision

Mobs Lite has a very straightforward API with a low learning curve. Yet, it is still powerful enough for developers to create unique and engaging mobs for their game, or just to custom tailor the traits and capabilities of those that are already included.

The Timekeeper Class

The Timekeeper class acts as a centralized dispatcher for all time-sensitive routines in Mobs Lite.

All mobs have a timekeeper object associated with them throughout their life cycle to ensure that all mob-related functions can execute at the correct interval. However, the Timekeeper constructor is global, so you can make use of its functionality with any entities.

  • Timekeeper( this )
    Instantiates and returns a new timekeeper object, with an optional meta table for use by callbacks (typically this will be a reference to the entity itself). Ideally the Timekeeper constructor will be called as soon as the LuaEntitySAO has been added to the environment.
on_activate(self)
        self.timekeeper = Timekeeper(self)
        :
end

The following methods are available:

  • timekeeper.start( period, name, func )
    Begins a new timer with the given name and period. The callback will execute no sooner than the next server step.

  • timekeeper.clear( name )
    Cancels an existing timer with the given name.

Four parameters are provided to the timer callback for each iteration:

  • this - the meta table that was originally passed to the constructor
  • cycles - the number of cycles that have accrued, beginning at 1
  • period - the interval between each cycle
  • elapsed - the elapsed time for all cycles
  • overrun - the overrun time from the last cycle

In order for the timers to be processed correctly, you must call the on_step() method of the timekeeper object during every server step. For example, in the case of entities:

on_step = function (dtime)
        local timers = self.timekeeper.on_step(dtime)
        :
end,

With a globalstep callback, it is similar albeit the timekeeper object will likely be local variable of the current script.

minetest.register_globalstep(function(dtime)
        local timers = globaltimer.on_step(dtime)
end)

Notice that the on_step method of the timekeeper object returns a table of timers. This can be useful for processing one or more timer events directly within the globalstep callback.

The Object Registry

The Object Registry provides an efficient means of iterating over players, avatars, and spawnitems. In contrast to the builtin API function minetest.get_objects_in_radius() that scans for all active objects in the environment, the object registry maintains its own lookup tables for each class of object, thereby alleviating wasted CPU cycles.

  • mobs.iterate_registry( source_pos, height, radius, classes ) Return an iterator function for traversing one or more classes of objects within a given range.

This example prints the names of all online players within 10 meters radius to the console:

local pos = player:get_pos()
for obj in mobs.iterate_registry(pos, 10.0, 5.0, {players = true})
        if obj ~= player then
                print(obj:get_player_name())
        end
end

Generally speaking, it is safe to call this function at any time, since the lookup tables are updated immediately when a player leaves or joins the game, a mob is spawned or despawned, etc. So there should be no risk of a race condition.

Acquiring Targets

All mobs whether hostile or friendly can seek out nearby targets at random including friends, foes, prey, etc. The likelihood of doing so is determined by the fear_factor property.

The self.target table usually consists of an object reference (player, avatar, wielditem, or spawnitem) in addition to the "last-viewed" position which is continuously updated until the target is no longer visible.

target = { pos = <vector>, obj = <ObjectRef> }

For environmental stimulii like sounds, smells, etc. in which there is no culpable object, then the target is stored as the "last-known" position

target = { pos = <vector> }

Alertness States

There are five builtin alertness states, any of which may be triggered depending on the current visibility of the target.

  • ignore - creature may walk or stand at random depending on the level of hunger
  • search - creature will survey its surroundings to locate the nearest suspect
  • follow - creature will walk toward the target and wait
  • attack - creature will run toward the target and fight
  • escape - creature will recoil and run away from the target

It is also possible to programmatically change the awareness state as a result of some other environmental condition, such as sounds and smells by use of the self:reset_alertness() method. See the section Responding to Stimulii for more information.

As you read through the remaining sections, this flow diagram may help to understand how this process works internally

Imgur

Viewing Capabilities

If you want your mobs to do more than just wander aimlessly around, then you will need to define the viewing capabilities for each possible alertness state in the alertness_states table.

Example:

alertness_states = {
        ignore = {view_offset = 8, view_radius = 10, view_height = 2, view_acuity = 2},
        search = {view_offset = 8, view_radius = 15, view_height = 4, view_acuity = 4, view_filter = view_filters.search_attack_weak},
        attack = {view_offset = 8, view_radius = 15, view_height = 4, view_acuity = 4, view_filter = view_filters.search_attack_weak},
        escape = {view_offset = 8, view_radius = 15, view_height = 2, view_acuity = 2},
},

The parameters for each alertness state are as follows:

  • view_offset - the distance from the target's center to the radial center of the view cone
  • view_radius - the radius of the view cone
  • view_height - the height of the view cone
  • view_acuity - the visual sensory strength with distance

View acuity follows an exponential decay curve, with zero being no visual sense at all and higher values being a sharper falloff with distance.

The view_filter parameter is an optional callback function for validating a new or existing target. It is expected to return the desired alertness state based on the target clarity and any number of other conditions.

Example:

view_filter = function ( self, obj, clarity, elapsed )
        return clarity < 0.5 and elapsed > 10 and "search" or "attack"
end,

Generally speaking you will rarely need to use this callback except for very complex state-change logic.

Awareness Stages

The alertness state of a mob is typically persistent until the target is no longer visible, at which time a lower alertness state will be engaged. For ephemeral alertness states like search and escape, a decay timer can induce state changes automatically after a timeout.

The awareness_stages table defines the order of state changes for each alertness state.

Example:

awareness_stages = {
        search = { decay = 12.0, pass_state = "attack", fail_state = "search", wait_state = "ignore" },
        attack = { decay = 0.0, pass_state = "attack", fail_state = "search", wait_state = "ignore" },
        escape = { decay = 12.0, pass_state = "escape", fail_state = "escape", wait_state = "ignore" },
},

The parameters for each awareness stage are as follows:

  • decay - the maximum duration of this alertness state, or nil for indefinite
  • wait_state - the alertness state to transition to after the decay timer expires
  • pass_state - the alertness state to transition to if the target is still visible
  • fail_state - the alertness state to transition to if the target is no longer visible

Note that if decay is nil and a view_filter exists for the corresponding alertness state, then the awareness stage entry is not necessary since the view filter result will take precedence.

Hunger Noise Function

Mobs will occasionally alternate between walking and standing when idle. But rather than relying on a random number generator, Mobs Lite takes advantage of 2d perlin noise function. In this way, changes in movement are gradual, giving a more natural, lifelike impression.

The frequency of walking vs. standing is determined by the Hunger Noise function, the output of which always ranges from -1.0 to 1.0. If greater than zero, the mob will walk, otherwise it will stand.

The parameters for the Hunger Noise function are specified in the hunger_params table of the entity definition:

  • offset - the perlin noise offset, where lower values increase the likelihood of standing and higher values increase the likelihood of walking (must be between -1.0 and 1.0)
  • spread - the perlin noise spread, where larger values increase the time it takes for changes in movement between walking and standing to occur (must be greater than 0).

Imgur

Example:

hunger_params = {offset = 0.3, spread = 2.0},

Behavior Modes

The normal behavior of a mob is determined by its neutral_state property. By default this is ignore, and it should rarely be set otherwise unless the mob is guaranteed to have a persistent target position or object.

The alerted behavior of a mob after acquiring a target is determined by itsoffense_state property. For monsters this will typically be attack, whereas for animals it may be either escape or follow.

The alerted behavior of a mob after being punched is determined by its defense_state property, or in the case of low hitpoints, by its retreat_state property.

The following table summarizes these behavior modes:

Watch Groups

Watch groups are a very powerful feature of Mobs Lite, allowing for mobs to continuously monitor specific players, spawnitems, and wielditems and adjust their alertness state accordingly.

For example, you might want a kitten to be attracted to raw meat. In that case you would add the following group to the mob definition.

watch_wielditems = {
        ["default:raw_meat"] = "follow"
},
watch_spawnitems = {
        ["default:raw_meat"] = "follow"
},

The kitten will now walk toward any player that is wielding raw meat, so long as the player is visible (see Viewing Capabilities above). Not only that, but if the raw meat is spawned or dropped as an item, the kitten will walk to that location as well.

In the case of food drops, you will probably want some more elaborate decision making routine than just "follow". For this purpose, you can supply a function that returns the alertness state.

watch_spawnitems = {
        ["default:raw_meat"] = function (self, target_obj, elapsed)
                -- decision making routine here
                return random(10) == 1 and "ignore" or "follow"
        end
},

Responding to Stimulii

By integrating with Axon, it is possible for mobs to respond to a variety of environmental stimulii, including sounds, smells, touch, heat, and cold.

Mobs Lite provide two wrapper functions to aid with making noises that mobs can sense:

  • mobs.make_noise( pos, radius, group, intensity )

    • pos - the position in world coordinates
    • radius - the propagation radius in meters
    • group - the stimulus group
    • intensity - the stimulus intensity
  • mobs.make_noise_repeat( pos, radius, interval, duration, group, intensity )

    • pos - the position in world coordinates
    • radius - the propagation radius in meters
    • interval - the interval between each firing
    • duration - the length of time to fire the stimulus
    • group - the stimulus group
    • intensity - the stimulus intensity

Note that any stimulus group may be specified, although noise_stim would be the most consistent for general purpose sounds.

To engage an alertness state within the noise_stim receptron, then simply call the self:reset_alertness() method. For example, to investigate a suspicious noise you could define a specialized on_reaction() callback for use by multiple mobs:

local function investigate_noise(self, intensity, direction)
        if self.state == "ignore" then
                -- general approximation of noise origin
                local pos = vector.add(self.pos, vector.multiply(direction, intensity))
                self:reset_alertness("search", {pos = pos})
                if self.sounds.attack then
                        self:play_sound(self.sounds.attack)
                end
        end
end