-
Notifications
You must be signed in to change notification settings - Fork 0
Usage Instructions
What exactly constitutes a stimulus is entirely up to the programmer's imagination! For example a "water_stim" stimulus could extinguish a fire arrow as soon as it enters a lake. Or an "alarm_stim" stimulus could could be applied to a claxon node, alerting nearby guards of an approaching intruder. Alternatively, a "noise_stim" stimulus could be generated on the fly as a frightened servant runs through the castle courtyard shouting "Help!"
Just about any objects in game can be programmed to sense the conditions of their surroundings and to respond accordingly by the use of sources and receptrons, a game mechanic inspired by the Act/React system of The Dark Engine
Every stimulus originates from a "source", which may be either persistent or dynamic. Only nodes can serve as a persistent source since the stimulus is emitted passively without any intervention. However, a "dynamic source" actively emits a stimulus, often in response to a specific condition -- possibly even another stimulus!
For obvious reasons, the easiest sources to work with are persistent. You define them once and Axon takes care of generating and propagating the stimulus automatically, behind the scenes.
The target of a stimulus is known as a "receptron". Only objects can react to a stimulus, and therefore only objects can have receptrons. However, an object can also relay a stimulus to a node or even another object via the use of a repeater. More on that later.
Persistent sources and source groups are registered in the axon/sources.lua
file. However, you may also include custom registrations in your own mod.
axon.register_source( node_name, stimulus_list )
Registers either a node or node group to act as a persistent source.
- node_name - the node or node group to be registered
- stimulus_list - a list of stimulus property tables (see below)
axon.register_source_group( node_group, node_names )
Registers a node group with a list of associated nodes
- node_group - the node group to be registered
- node_names - the names of all nodes to be included the node group
A stimulus property table varies depending on the transmission method. All property tables, however, include at least the following three fields: "group", "propagator", and "intensity".
- group - the group in which this stimulus belongs (see Damage Mechanism Integration for an explanation)
- propagator - the transmission method: radiation, emission, immersion, or contact
- intensity - the peak amplitude, expressed as positive integer (fractional values are rounded)
It is important not to confuse the terms "source group" and "stimulus group". A source group is merely a synonym for a node group that acts as persistent source. In other words, it is a grouping of nodes that emit the same stimulii. A stimulus group, in contrast, is the group in which any number of related stimulii may belong.
Below are the four available propagators and their respective fields:
Radiation Propagator
Propagates the stimulus within a spherical area, the intensity curve of which is based on the total number of surrounding sources in the area.
- period - the interval between firing
- radius - the propagation radius
- chance - the probability of firing at each interval
- power - the intensity coefficient, ranging from 0 to 1
Emission Propagator
Propagates the stimulus within a spherical area, the intensity curve of which is based on the distance vs radius from the nearest source in the area.
- period - the interval between firing
- chance - the probability of firing at each interval
- radius - the propagation radius
- slope - the intensity falloff, ranging from 0 and 1
Immersion Propagator
Propagates the stimulus only inside the node boundaries
- period - the interval between firing
- chance - the probability of firing at each interval
Contact Propagator
Propagates a stimulus along the outer edges of the node
- period - the interval between firing
- chance - the probability of firing at each interval
- power - the intensity coefficient, ranging from 0 to 1
The intensity coefficient curve for the contact and radiation propagators can be visualized as follows, where the colored lines depict the resulting damage in relation to the input intensity:
The intensity falloff curve for the emission propagator can similarly be visualized as follows:
Giving NPCs sensory abilities is as easy as a few lines of code. First and foremost, you will need to inherit the AxonObject
superclass. The best place to do this is within the on_activate()
callback of your entity.
on_activate = function ( self, dtime, staticdata )
AxonObject( self )
:
end
Next, add a receptrons
table to the entity prototype:
:
receptrons = {
water_stim = { sensitivity = 50, min_intensity = 2, max_intensity = nil, on_reaction = function ( self, intensity, direction )
self:extinguish( intensity )
end },
}
This is an example of a single receptron that responds to stimulii from the "water_stim" group having an intensity above 2 with an average 50% probability of reception.
Note that if the receptrons
table does not exist, then Axon will assume that the entity is inert to all stimulii. In some cases that may be what you want, particularly if the entity acts exclusively as a dynamic source.
Receptrons are defined according to the following properties, all of which are optional except the callback:
- sensitivity - the reception probability, ranging from 0 to 100 (default 100)
- min_intensity - the minimum intensity (default 1)
- max_intensity - the maximum intensity (default 63336)
- on_reaction - callback to be triggered when the stimulus is received
The on_reaction()
callback determines exactly how the entity should respond to the stimulus. The following parameters are available:
- self - reference to the parent entity
- intensity - the perceived intensity of the stimulus, which may be less than the original intensity
- direction - the direction of the stimulus as a unit vector in relation to the entity
Note that in the case of a directly propagated stimuilus, the direction parameter will be nil.
Stimulii can be generated anytime and anywhere by means of stimulus generator API functions. This is useful for programmatic events, such as an unbearable stench emanating from a fresh zombie corpse.
axon.generate_direct_stimulus( obj, groups )
Generates a stimulus that propagates directly to a single object
- obj - the object to receive the stimulus
- groups - a table of stimulus groups and their respective intensities
axon.generate_radial_stimulus( pos, radius, speed, slope, groups, classes )
Generates a stimulus that propagates radially to one or more objects
- pos - the position in world coordinates
- radius - the propagation radius in meters
- speed - the propagation speed in meters per second, or 0 for instant
- slope - the propagation slope expressed as a number between 0 and 1, where zero is linear, 1 is constant, and fractional values are an exponential falloff
- roups - a table of stimulus groups and their respective intensities
- classes - the membership set of ObjectRef classes to iterate: players, avatars, objects, spawnitems
Using either of these functions with the Timekeeper class or with a node timer makes it possible to repeatedly generate a stimulus over a period of time. In fact, that is exactly how noises are transmitted by the Mobs Lite API! Here is the implementation for mobs.make_noise_repeat()
function:
mobs.make_noise_repeat = function ( pos, radius, interval, duration, group, intensity )
globaltimer.start_now( interval, "noise" .. next_noise_id, function ( this, cycles, period, elapsed )
if elapsed >= duration then return true end -- we're finished, so cancel timer
axon.generate_radial_stimulus( pos, radius, 0.0, 0.0, { [group] = intensity }, { avatars = true } )
end )
next_noise_id = next_noise_id + 1
end
If an entity has been subclassed as an AxonObject, then the stimulus generators listed above are available as methods of the entity:
self:generate_direct_stimulus( obj, groups )
Generates a stimulus that propagates directly to a single object
self:generate_radial_stimulus( radius, speed, slope, groups, classes )
Generates a stimulus that propagates radially to one or more objects
One important caveat: It is easy to create a cascading feedback loop between multiple dynamic sources. This only applies, of course, if you call either of the generator functions within a receptron. In such case, you should raise a flag or set a decay timer to avoid potential recursion. As a safeguard, Axon will stop events from propagating more than 5 levels deep. An event in Axon refers to the transmission of a stimulus from source to receptron.
Damage Mechanism Integration
Axon uses the Minetest damage system for propagation of stimulii. This has the benefit that the entire game mechanic is object agnostic. Hence players, mobs, and even spawned items can react to the same stimulii, if desired. To achive this, Axon overrides the necessary callbacks, passing only the punch events that are not handled by a corresponding receptron.
If an on_reaction()
callback returns nil or false, then no other callbacks will be fired. By returning true, however, then additional stimulus groups can be be processed by other receptrons.
Performance Considerations
Axon is engineered from the ground up to be very lightweight. This is the reason that I opted to forego active block modifiers. Even minetest.get_objects_in_radius()
has been replaced with the more efficient registry iterator from Mobs Lite, allowing for ObjectRef lookups by membership set. Moreover, Axon only propagates stimulii to objects that have a corresponding receptron, so there is no additional overhead simply by registering many persistent sources.
With that said, it is still a good idea to be mindful when implementing sources and receptrons so as to squeeze every ounce of performance possible from your server. The quickest and easiest optimization off the bat is to combine as many sources as possible into a single source group. This eliminates redundant node checks and wasted CPU cycles.
For example say that a torch and an active furnace both produce the same degree of heat on contact. You could register them to act as multiple persistent sources.
axon.register_source( "default:torch", {
{ group = "heat_stim", propagator = "contact", intensity = 3, chance = 2, period = 1.0, power = 0.8 },
} )
axon.register_source( "default:furnace_active", {
{ group = "heat_stim", propagator = "contact", intensity = 3, chance = 2, period = 1.0, power = 0.8 },
} )
Better still is simply to combine them so they act as a single persistent source group:
axon.register_source( "group:heat_source", {
{ group = "heat_stim", propagator = "contact", intensity = 3, chance = 2, period = 1.0, power = 0.8 },
} )
axon.register_source_group( "heat_source", { "default:torch", "default:furnace_active" } )
When selecting which transmission method to it use for persistent sources, it is worth noting that immersion and contact propagators will almost always perform better than radiation and emission propagators due to their limited range. So when possible, consider opting for the former instead of the latter.