From 893f541d137fc567f2c2f89c51b5d410cecb5322 Mon Sep 17 00:00:00 2001 From: Leslie Krause Date: Thu, 14 May 2020 17:36:47 -0400 Subject: [PATCH] Build 01 - initial beta version --- README.txt | 45 ++++++++ init.lua | 305 ++++++++++++++++++++++++++++++++++++++++++++++++++++ mod.conf | 4 + sources.lua | 20 ++++ 4 files changed, 374 insertions(+) create mode 100644 README.txt create mode 100644 init.lua create mode 100644 mod.conf create mode 100644 sources.lua diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..fe1b05d --- /dev/null +++ b/README.txt @@ -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 diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..7c97e9c --- /dev/null +++ b/init.lua @@ -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" ) diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..a06d89b --- /dev/null +++ b/mod.conf @@ -0,0 +1,4 @@ +name = axon +title = Axon +author = sorcerykid +license = LGPL-3.0 diff --git a/sources.lua b/sources.lua new file mode 100644 index 0000000..f640758 --- /dev/null +++ b/sources.lua @@ -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" } )