- initial beta version
This commit is contained in:
Leslie Krause 2020-05-14 17:36:47 -04:00
commit 893f541d13
4 changed files with 374 additions and 0 deletions

45
README.txt Normal file
View File

@ -0,0 +1,45 @@
Axon Event Propagation Mod v1.0
By Leslie E. Krause
Axon is a lightweight but powerful event propagation framework for Minetest, affording a
new degree of creative freedom for game designers and mod developers. It is inspired in
great part by the Act/React system of the Dark Engine by Looking Glass Studios.
For complete usage instructions, please refer to the forum topic:
https://forum.minetest.net/viewtopic.php?f=9&t=24721
Repository
----------------------
Browse source code...
https://bitbucket.org/sorcerykid/axon
Download archive...
https://bitbucket.org/sorcerykid/axon/get/master.zip
https://bitbucket.org/sorcerykid/axon/get/master.tar.gz
Installation
----------------------
1) Unzip the archive into the mods directory of your game.
2) Rename the axon-master directory to "axon".
3) Add "axon" as a dependency to any mods using the API.
Source Code License
----------------------------------------------------------
GNU Lesser General Public License v3 (LGPL-3.0)
Copyright (c) 2020, Leslie E. Krause (leslie@searstower.org)
This program is free software; you can redistribute it and/or modify it under the terms of
the GNU Lesser General Public License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
http://www.gnu.org/licenses/lgpl-2.1.html

305
init.lua Normal file
View File

@ -0,0 +1,305 @@
--------------------------------------------------------
-- Minetest :: Axon Event Propagation Mod (axon)
--
-- See README.txt for licensing and release notes.
-- Copyright (c) 2019-2020, Leslie E. Krause
--
-- ./games/minetest_game/mods/axon/init.lua
--------------------------------------------------------
--local S1,S1_ = Stopwatch( "axon" )
axon = { }
local sources = { }
local stack_size = 0
--------------------
local random = math.random
local pow = math.pow
local ceil = math.ceil
local function check_limits( v, min_v, max_v )
return v >= min_v and v <= max_v
end
local function clamp( val, min, max )
return val < min and min or val > max and max or val
end
local function get_power_decrease( scale, power, ratio )
return ratio <= 1 - scale and 1.0 or
max( 1 - pow( ( scale + ratio - 1 ) / scale, 1 + power ), 0 )
end
local function get_power_increase( scale, power, ratio )
return ratio >= scale and 1.0 or
1 - pow( ( scale - ratio ) / scale, 1 + power )
end
local function get_signal_strength( max_value, cur_value, slope )
return math.pow( cur_value / max_value, 1 - clamp( slope, 0, 1 ) )
end
local function punch_object( obj, groups, pos )
stack_size = stack_size + 1
assert( stack_size < 10, "punch_object( ): Aborting callback relay due to possible recursion" )
obj:punch( obj, 1.0, {
full_punch_interval = 1.0,
damage_groups = groups
}, pos and vector.direction( obj:get_pos( ), pos ) or nil )
stack_size = stack_size - 1
end
--------------------
axon.register_source = function ( node_name, stimulus_list )
if not sources[ node_name ] then
sources[ node_name ] = { }
end
for i, v in pairs( stimulus_list ) do
local stimulus
if v.class == "contact" then
stimulus = ContactStimulus( node_name, v.group, v.intensity, v.chance, v.period, v.power )
elseif v.class == "radiation" then
stimulus = RadiationStimulus( node_name, v.group, v.intensity, v.chance, v.period, v.radius, v.scale, v.power, v.max_count )
elseif v.class == "immersion" then
stimulus = ImmersionStimulus( node_name, v.group, v.intensity, v.chance, v.period )
end
table.insert( sources[ node_name ], stimulus )
end
end
axon.register_source_group = function ( node_group, node_names )
for i, v in ipairs( node_names ) do
local groups = minetest.regi stered_nodes[ v ].groups
groups[ node_group ] = 1
minetest.override_item( v, { groups = groups } )
end
end
axon.generate_direct_stimulus = function ( obj, groups )
punch_object( obj, groups )
end
axon.generate_radial_stimulus = function ( pos, radius, speed, slope, chance, groups, classes )
for obj in mobs.iterate_registry( pos, radius, radius, classes ) do
local length = vector.distance( pos, obj:get_pos( ) )
if length <= radius and random( chance ) == 1 then
local damage_groups = { }
for k, v in pairs( groups ) do
damage_groups[ k ] = ceil( v * get_signal_strength( radius, radius - length, slope ) )
end
if speed > 0 then
minetest.after( length / speed, function ( )
punch_object( obj, damage_groups, pos )
end )
else
punch_object( obj, damage_groups, pos )
end
end
end
end
--------------------
local function ContactStimulus( node_name, group, intensity, chance, period, power )
local self = { }
self.group = group
self.class = "contact"
self.period = period
self.on_action = function ( source_pos, target_obj )
if random( chance ) == 1 then
local touch_counts = minetest.count_nodes_in_area(
vector.add( source_pos, -0.5 ), vector.add( source_pos, 0.5 ), { node_name }, true )
local count = touch_counts[ node_name ]
if count > 0 then
local damage = intensity * pow( count, power )
target_obj:punch( target_obj, period, {
full_punch_interval = period,
damage_groups = { [group] = damage },
}, nil )
end
end
end
return self
vend
local function RadiationStimulus( node_name, group, intensity, chance, period, radius, scale, power, max_count )
local self = { }
self.group = group
self.class = "radiation"
self.period = period
self.on_action = function ( source_pos, target_obj )
if random( chance ) == 1 then
local touch_counts = minetest.count_nodes_in_area(
vector.add( source_pos, -radius ), vector.add( source_pos, radius ), { node_name }, true )
local count = touch_counts[ node_name ]
if count > 0 then
local damage = intensity * get_power_increase( scale, power, count / max_count )
target_obj:punch( target_obj, period, {
full_punch_interval = period,
damage_groups = { [group] = damage },
}, nil )
end
end
end
return self
end
local function ImmersionStimulus( node_name, group, intensity, chance, period )
local self = { }
self.group = group
self.class = "immersion"
self.period = period
self.on_action = function ( source_pos, target_obj )
if random( chance ) == 1 then
local node = minetest.get_node( vector.round( source_pos ) )
local node_group = string.match( node_name, "^group:(.+)" )
if node.name == node_name or node_group and minetest.get_item_group( node.name, node_group ) > 0 then
target_obj:punch( target_obj, period, {
full_punch_interval = period,
damage_groups = { [group] = intensity },
}, nil )
end
end
end
return self
end
local function AxonPropagator( obj )
local event_defs = { }
local clock = 0.0
local delay = 0.0
local self = { }
self.start = function ( stimulus )
table.insert( event_defs, {
group = stimulus.group,
period = stimulus.period,
expiry = clock + stimulus.period,
started = clock,
on_action = stimulus.on_action
} )
end
self.on_step = function ( dtime, pos )
clock = clock + dtime
for i, v in ipairs( event_defs ) do
if clock >= v.expiry and clock > v.started then
v.expiry = clock + v.period
v.on_action( pos, obj )
end
end
end
return self
end
function AxonObject( self, armor_groups )
local old_on_step = self.on_step
local old_on_punch = self.on_punch
local propagator = AxonPropagator( self.object )
if not self.receptrons then
self.receptrons = { }
end
-- initialize armor groups
if not armor_groups then
armor_groups = { }
end
for k, v in pairs( self.receptrons ) do
armor_groups[ k ] = v.sensitivity or 100
end
self.object:set_armor_groups( armor_groups )
-- start stimulus propagators
for k, v in pairs( sources ) do
for idx, stimulus in ipairs( v ) do
-- ignore stimulii without a receptron
if self.receptrons[ stimulus.group ] then
propagator.start( stimulus )
end
end
end
self.on_step = function ( self, dtime, pos, ... )
propagator.on_step( dtime, pos )
old_on_step( self, dtime, pos, ... )
end
self.generate_direct_stimulus = function ( self, obj, groups )
if obj ~= self.object then
punch_object( obj, groups )
end
end
self.generate_radial_stimulus = function ( self, radius, speed, slope, chance, groups, classes )
local pos = self.object:get_pos( )
for obj in mobs.iterate_registry( pos, radius, radius, classes ) do
local length = vector.distance( pos, obj:get_pos( ) )
if obj ~= self.object and length <= radius and random( chance ) == 1 then
local damage_groups = { }
for k, v in pairs( groups ) do
damage_groups[ k ] = v * get_signal_strength( radius, radius - length, slope )
end
if speed > 0 then
minetest.after( length / speed, function ( )
punch_object( obj, damage_groups )
end )
else
punch_object( obj, damage_groups )
end
end
end
end
self.on_punch = function ( self, puncher, time_from_last_punch, tool_capabilities, direction, damage )
print( "self:onpunch():", self.object, self.name )
if puncher == self.object then
-- filter and relay the incoming stimulus
for k, v in pairs( tool_capabilities.damage_groups ) do
local receptron = self.receptrons[ k ]
if receptron and check_limits( v, receptron.min_intensity or 1, receptron.max_intensity or 65536 ) then
print( "-> on_reaction(self)" )
-- if receptron returns false, then stimulus was handled
print( "DIR", minetest.pos_to_string( direction ) )
if not receptron.on_reaction( self, v, direction ) then return true end
end
end
end
print( "-> old_on_punch(self)" )
-- if stimulus wasn't handled by a receptron, fallback to on_punch method
return old_on_punch( self, puncher, time_from_last_punch, tool_capabilities, direction, damage )
end
end
--------------------
dofile( minetest.get_modpath( "axon" ) .. "/sources.lua" )

4
mod.conf Normal file
View File

@ -0,0 +1,4 @@
name = axon
title = Axon
author = sorcerykid
license = LGPL-3.0

20
sources.lua Normal file
View File

@ -0,0 +1,20 @@
-- This is a sample source definition file. You will want to modify this for your particular game.
axon.register_source( "group:lava_source", {
{ group = "heat_stim", class = "radiation", intensity = 30, chance = 1, period = 1.5, radius = 5.0, scale = 1.0, power = 0.0, max_count = 6 },
{ group = "lava_stim", class = "immersion", intensity = 10, chance = 1, period = 0.5 },
} )
axon.register_source( "group:water_source", {
{ group = "water_stim", class = "immersion", intensity = 5, chance = 1, period = 0.5 },
} )
axon.register_source( "group:heat_source", {
{ group = "heat_stim", class = "contact", intensity = 3, chance = 2, period = 1.0, power = 0.8 },
} )
axon.register_source_group( "lava_source", { "default:lava_source", "default:lava_flowing" } )
axon.register_source_group( "heat_source", { "default:torch", "default:furnace_active", "fire:permanent_flame" } )
axon.register_source_group( "water_source", { "default:water_source", "default:water_flowing" } )