Implement comparator accurately and remove WIP, remove ABMs for comparators and make updates triggered instead of polled, changes to redstone power transmission

This commit is contained in:
teknomunk 2024-04-14 22:35:32 +00:00
parent e970a5f414
commit 13888236d6
5 changed files with 109 additions and 126 deletions

View File

@ -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

View File

@ -1,3 +1,3 @@
name = mcl_comparators
depends = mcl_wip, mesecons, mcl_sounds
depends = mesecons, mcl_sounds, vl_redstone
optional_depends = doc, screwdriver

View File

@ -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

View File

@ -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(),

View File

@ -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