From 13888236d6dff85e350c58841c55777518262ec9 Mon Sep 17 00:00:00 2001 From: teknomunk Date: Sun, 14 Apr 2024 22:35:32 +0000 Subject: [PATCH] Implement comparator accurately and remove WIP, remove ABMs for comparators and make updates triggered instead of polled, changes to redstone power transmission --- mods/ITEMS/REDSTONE/mcl_comparators/init.lua | 173 +++++++------------ mods/ITEMS/REDSTONE/mcl_comparators/mod.conf | 2 +- mods/ITEMS/REDSTONE/vl_redstone/init.lua | 41 +++-- mods/ITEMS/mcl_hoppers/init.lua | 17 +- mods/ITEMS/mcl_hoppers/mod.conf | 2 +- 5 files changed, 109 insertions(+), 126 deletions(-) diff --git a/mods/ITEMS/REDSTONE/mcl_comparators/init.lua b/mods/ITEMS/REDSTONE/mcl_comparators/init.lua index 01b42c340..2ad8257b9 100644 --- a/mods/ITEMS/REDSTONE/mcl_comparators/init.lua +++ b/mods/ITEMS/REDSTONE/mcl_comparators/init.lua @@ -1,4 +1,6 @@ local S = minetest.get_translator(minetest.get_current_modname()) +mcl_comparators = {} +local mod = mcl_comparators -- Functions that get the input/output rules of the comparator @@ -10,7 +12,6 @@ local function comparator_get_output_rules(node) return rules end - local function comparator_get_input_rules(node) local rules = { -- we rely on this order in update_self below @@ -24,117 +25,73 @@ local function comparator_get_input_rules(node) return rules end - --- Functions that are called after the delay time - -local function comparator_turnon(params) - local rules = comparator_get_output_rules(params.node) - mesecon.receptor_on(params.pos, rules) -end - - -local function comparator_turnoff(params) - local rules = comparator_get_output_rules(params.node) - mesecon.receptor_off(params.pos, rules) -end - - --- Functions that set the correct node type an schedule a turnon/off - -local function comparator_activate(pos, node) - local def = minetest.registered_nodes[node.name] - local onstate = def.comparator_onstate - if onstate then - minetest.swap_node(pos, { name = onstate, param2 = node.param2 }) - end - minetest.after(0.1, comparator_turnon , {pos = pos, node = node}) -end - - -local function comparator_deactivate(pos, node) - local def = minetest.registered_nodes[node.name] - local offstate = def.comparator_offstate - if offstate then - minetest.swap_node(pos, { name = offstate, param2 = node.param2 }) - end - minetest.after(0.1, comparator_turnoff, {pos = pos, node = node}) -end - - --- weather pos has an inventory that contains at least one item -local function container_inventory_nonempty(pos) - local invnode = minetest.get_node(pos) - local invnodedef = minetest.registered_nodes[invnode.name] - -- Ignore stale nodes - if not invnodedef then return false end - - -- Only accept containers. When a container is dug, it's inventory - -- seems to stay. and we don't want to accept the inventory of an air - -- block - if not invnodedef.groups.container then return false end - - local inv = minetest.get_inventory({type="node", pos=pos}) - if not inv then return false end - - for listname, _ in pairs(inv:get_lists()) do - if not inv:is_empty(listname) then return true end - end - - return false -end - --- weather pos has an constant signal output for the comparator -local function static_signal_output(pos) - local node = minetest.get_node(pos) - local g = minetest.get_item_group(node.name, "comparator_signal") - return g > 0 -end - --- whether the comparator should be on according to its inputs -local function comparator_desired_on(pos, node) - local my_input_rules = comparator_get_input_rules(node); - local back_rule = my_input_rules[1] - local state - if back_rule then - local back_pos = vector.add(pos, back_rule) - state = mesecon.is_power_on(back_pos) or container_inventory_nonempty(back_pos) or static_signal_output(back_pos) - end - - -- if back input if off, we don't need to check side inputs - if not state then return false end - - -- without power levels, side inputs have no influence on output in compare - -- mode - local mode = minetest.registered_nodes[node.name].comparator_mode - if mode == "comp" then return state end - - -- subtract mode, subtract max(side_inputs) from back input - local side_state = false - for ri = 2,3 do - if my_input_rules[ri] then - side_state = mesecon.is_power_on(vector.add(pos, my_input_rules[ri])) - end - if side_state then break end - end - -- state is known to be true - return not side_state -end - - +local POSSIBLE_COMPARATOR_POSITIONS = { + vector.new( 1,0, 0), + vector.new(-1,0, 0), + vector.new( 0,0, 1), + vector.new( 0,0,-1), +} -- update comparator state, if needed local function update_self(pos, node) node = node or minetest.get_node(pos) - local old_state = mesecon.is_receptor_on(node.name) - local new_state = comparator_desired_on(pos, node) - if new_state ~= old_state then - if new_state then - comparator_activate(pos, node) - else - comparator_deactivate(pos, node) + + -- Find the node we are pointing at + local input_rules = comparator_get_input_rules(node); + local back_rule = input_rules[1] + local back_pos = vector.add(pos, back_rule) + local back_node = minetest.get_node(back_pos) + local back_nodedef = minetest.registered_nodes[back_node.name] + + -- Get the comparator mode + local mode = minetest.registered_nodes[node.name].comparator_mode + + -- Get a comparator reading from the block at the back of the comparator + local power_level = 0 + if back_nodedef and back_nodedef._mcl_comparators_get_reading then + power_level = back_nodedef._mcl_comparators_get_reading(back_pos) + end + + -- Get the maximum side power level + local side_power_level = 0 + for i=2,3 do + local pl = vl_redstone.get_power_level(vector.add(pos,input_rules[i])) + if pl > side_power_level then + side_power_level = pl + end + end + + -- Apply subtraction or comparison + if mode == "sub" then + power_level = power_level - side_power_level + if power_level < 0 then power_level = 0 end + elseif mode == "comp" then + if side_power_level > power_level then + power_level = 0 + end + end + + vl_redstone.set_power(pos, power_level) +end + +function mod.trigger_update(pos) + -- try to find a comparator with the back rule leading to pos + for i = 1,#POSSIBLE_COMPARATOR_POSITIONS do + local candidate = vector.add(pos, POSSIBLE_COMPARATOR_POSITIONS[i]) + local node = minetest.get_node(candidate) + if minetest.get_item_group(node.name,"mcl_comparator") ~= 0 then + update_self(candidate, node) end end end +function mod.read_inventory(inv, inv_name) + local stacks = inv:get_list(inv_name) + local count = 0 + for i=1,#stacks do + count = count + ( stacks[i]:get_count() / stacks[i]:get_stack_max() ) + end + return math.floor(count * 16 / 5) +end -- compute tile depending on state and mode local function get_tiles(state, mode) @@ -215,6 +172,7 @@ local groups = { destroy_by_lava_flow = 1, dig_by_piston = 1, attached_node = 1, + mcl_comparator = 1, } local on_rotate @@ -309,7 +267,7 @@ for _, mode in pairs{"comp", "sub"} do end minetest.register_node(nodename, nodedef) - mcl_wip.register_wip_item(nodename) + --mcl_wip.register_wip_item(nodename) end end @@ -327,6 +285,7 @@ minetest.register_craft({ } }) +--[[ -- Register active block handlers minetest.register_abm({ label = "Comparator signal input check (comparator is off)", @@ -352,7 +311,7 @@ minetest.register_abm({ chance = 1, action = update_self, }) - +]] -- Add entry aliases for the Help if minetest.get_modpath("doc") then diff --git a/mods/ITEMS/REDSTONE/mcl_comparators/mod.conf b/mods/ITEMS/REDSTONE/mcl_comparators/mod.conf index 100e42814..35ddfbc21 100644 --- a/mods/ITEMS/REDSTONE/mcl_comparators/mod.conf +++ b/mods/ITEMS/REDSTONE/mcl_comparators/mod.conf @@ -1,3 +1,3 @@ name = mcl_comparators -depends = mcl_wip, mesecons, mcl_sounds +depends = mesecons, mcl_sounds, vl_redstone optional_depends = doc, screwdriver diff --git a/mods/ITEMS/REDSTONE/vl_redstone/init.lua b/mods/ITEMS/REDSTONE/vl_redstone/init.lua index 1a68ff23b..f6fd00d62 100644 --- a/mods/ITEMS/REDSTONE/vl_redstone/init.lua +++ b/mods/ITEMS/REDSTONE/vl_redstone/init.lua @@ -4,7 +4,6 @@ vl_redstone = {} local mod = vl_redstone local REDSTONE_POWER_META = modname .. ".power" -local REDSTONE_POWER_META_LAST_STATE = modname .. ".last-power" local REDSTONE_POWER_META_SOURCE = REDSTONE_POWER_META.."." local function update_sink(pos) @@ -33,10 +32,10 @@ local function update_sink(pos) end end - local last_strength = meta:get_int(REDSTONE_POWER_META_LAST_STATE) + local last_strength = meta:get_int(REDSTONE_POWER_META) if last_strength ~= strength then - print("Updating "..node.name.." at "..vector.to_string(pos).."("..tostring(last_strength).."->"..tostring(strength)..")") + --print("Updating "..node.name.." at "..vector.to_string(pos).."("..tostring(last_strength).."->"..tostring(strength)..")") -- Inform the node of changes if strength > 0 then -- Handle activation @@ -54,7 +53,7 @@ local function update_sink(pos) -- TODO: handle signal level change notification -- Update the state as the last thing in case there is a crash in the above code - meta:set_int(REDSTONE_POWER_META_LAST_STATE, strength) + meta:set_int(REDSTONE_POWER_META, strength) end end @@ -90,34 +89,35 @@ local function get_positions_from_node_rules(pos, rules_type, list, powered) rules = POWERED_BLOCK_RULES end - print("rules="..dump(rules)) + --print("rules="..dump(rules)) -- Convert to absolute positions for i=1,#rules do local next_pos = vector.add(pos, rules[i]) local next_pos_str = vector.to_string(next_pos) - print("\tnext: "..next_pos_str..", prev="..tostring(list[next_pos_str])) + --print("\tnext: "..next_pos_str..", prev="..tostring(list[next_pos_str])) list[next_pos_str] = true -- Power solid blocks if rules[i].spread then powered[next_pos_str] = true - print("powering "..next_pos_str) + --print("powering "..next_pos_str) end end return list end -vl_scheduler.register_function("vl_redstone:flow_power",function(task, source_pos, strength, distance) - print("Flowing lv"..tostring(strength).." power from "..vector.to_string(source_pos).." for "..tostring(distance).." blocks") +vl_scheduler.register_function("vl_redstone:flow_power",function(task, source_pos, source_strength, distance) + print("Flowing lv"..tostring(source_strength).." power from "..vector.to_string(source_pos).." for "..tostring(distance).." blocks") local processed = {} local powered = {} local source_pos_str = vector.to_string(source_pos) + processed[source_pos_str] = true -- Update the source node's redstone power local meta = minetest.get_meta(source_pos) - meta:set_int(REDSTONE_POWER_META, strength) + meta:set_int(REDSTONE_POWER_META, source_strength) -- Get rules local list = {} @@ -126,11 +126,11 @@ vl_scheduler.register_function("vl_redstone:flow_power",function(task, source_po for i=1,distance do local next_list = {} - local strength = strength - (i - 1) + local strength = source_strength - (i - 1) if strength < 0 then strength = 0 end for pos_str,dir in pairs(list) do - print("Processing "..pos_str) + --print("Processing "..pos_str) if not processed[pos_str] then processed[pos_str] = true @@ -140,7 +140,7 @@ vl_scheduler.register_function("vl_redstone:flow_power",function(task, source_po -- Update node power directly meta:set_int(REDSTONE_POWER_META.."."..source_pos_str, strength) - print("pos="..vector.to_string(pos)..", strength="..tostring(strength)) + --print("pos="..vector.to_string(pos)..", strength="..tostring(strength)) -- handle spread get_positions_from_node_rules(pos, "conductor", next_list, powered) @@ -158,10 +158,23 @@ end) function vl_redstone.set_power(pos, strength) local meta = minetest.get_meta(pos) local distance = meta:get_int(REDSTONE_POWER_META) + + -- Don't perform an update if the power level is the same as before + if distance == strength then return end + + print("previous="..tostring(distance)..", new="..tostring(strength)) + + -- Make the update distance the maximum of the new strength and the old strength if distance < strength then distance = strength end - vl_scheduler.add_task(0, "vl_redstone:flow_power", 2, {pos, strength, distance}) + -- Schedule an update + vl_scheduler.add_task(0, "vl_redstone:flow_power", 2, {pos, strength, distance + 1}) +end + +function vl_redstone.get_power_level(pos) + local meta = minetest.get_meta(pos) + return meta:get_int(REDSTONE_POWER_META) end diff --git a/mods/ITEMS/mcl_hoppers/init.lua b/mods/ITEMS/mcl_hoppers/init.lua index 07dd52089..fa12c43bd 100644 --- a/mods/ITEMS/mcl_hoppers/init.lua +++ b/mods/ITEMS/mcl_hoppers/init.lua @@ -283,14 +283,17 @@ local def_hopper = { end end, on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + mcl_comparators.trigger_update(pos) minetest.log("action", player:get_player_name() .. " moves stuff in mcl_hoppers at " .. minetest.pos_to_string(pos)) end, on_metadata_inventory_put = function(pos, listname, index, stack, player) + mcl_comparators.trigger_update(pos) minetest.log("action", player:get_player_name() .. " moves stuff to mcl_hoppers at " .. minetest.pos_to_string(pos)) end, on_metadata_inventory_take = function(pos, listname, index, stack, player) + mcl_comparators.trigger_update(pos) minetest.log("action", player:get_player_name() .. " takes stuff from mcl_hoppers at " .. minetest.pos_to_string(pos)) end, @@ -334,6 +337,11 @@ local def_hopper = { return true end, + _mcl_comparators_get_reading = function (pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + return mcl_comparators.read_inventory(inv, "main") + end, sounds = mcl_sounds.node_sound_metal_defaults(), _mcl_blast_resistance = 4.8, @@ -525,16 +533,19 @@ local def_hopper_side = { end end, on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + mcl_comparators.trigger_update(pos) minetest.log("action", player:get_player_name() .. - " moves stuff in mcl_hoppers at " .. minetest.pos_to_string(pos)) + " moves stuff in mcl_hoppers at (1) " .. minetest.pos_to_string(pos)) end, on_metadata_inventory_put = function(pos, listname, index, stack, player) + mcl_comparators.trigger_update(pos) minetest.log("action", player:get_player_name() .. - " moves stuff to mcl_hoppers at " .. minetest.pos_to_string(pos)) + " moves stuff to mcl_hoppers at (2) " .. minetest.pos_to_string(pos)) end, on_metadata_inventory_take = function(pos, listname, index, stack, player) + mcl_comparators.trigger_update(pos) minetest.log("action", player:get_player_name() .. - " takes stuff from mcl_hoppers at " .. minetest.pos_to_string(pos)) + " takes stuff from mcl_hoppers at (3) " .. minetest.pos_to_string(pos)) end, on_rotate = on_rotate, sounds = mcl_sounds.node_sound_metal_defaults(), diff --git a/mods/ITEMS/mcl_hoppers/mod.conf b/mods/ITEMS/mcl_hoppers/mod.conf index 639a5f59e..cb71518a1 100644 --- a/mods/ITEMS/mcl_hoppers/mod.conf +++ b/mods/ITEMS/mcl_hoppers/mod.conf @@ -1,4 +1,4 @@ name = mcl_hoppers description = It's just a clone of Minecraft hoppers, functions nearly identical to them minus mesecons making them stop and the way they're placed. -depends = mcl_core, mcl_formspec, mcl_sounds, mcl_util, mcl_dye +depends = mcl_core, mcl_formspec, mcl_sounds, mcl_util, mcl_dye, mcl_comparators optional_depends = doc, screwdriver