waxtatect f9431c5e09 c the commit
interact directly with machine blocks when marks displayed
hold sneak and punch a block to abort interactive setup
allow removing keypad password
add place sounds for mover in normal, dig and transport modes
somewhat rework player punch in space
code maintenance
2024-04-23 15:54:59 +02:00

385 lines
14 KiB
Lua

-- (c) 2015-2016 rnd
-- Copyright (C) 2022-2024 мтест
-- See README.md for license details
local F, S = basic_machines.F, basic_machines.S
local machines_TTL = basic_machines.properties.machines_TTL
local machines_minstep = basic_machines.properties.machines_minstep
local machines_timer = basic_machines.properties.machines_timer
local mover_no_large_stacks = basic_machines.settings.mover_no_large_stacks
local string_byte = string.byte
local signs -- when activated with keypad their text will be updated
local use_signs_lib = minetest.global_exists("signs_lib")
local use_default = basic_machines.use_default
local use_unifieddyes = minetest.global_exists("unifieddyes")
if use_signs_lib then
signs = {}
for _, sign_name in ipairs(signs_lib.lbm_restore_nodes or {}) do
signs[sign_name] = true
end
elseif use_default then
signs = {
["default:sign_wall_steel"] = true,
["default:sign_wall_wood"] = true
}
else
signs = {}
end
-- position, time to live (how many times can signal travel before vanishing to prevent infinite recursion),
-- do we want to stop repeating
basic_machines.use_keypad = function(pos, ttl, reset, reset_msg)
if ttl < 1 then return end
local meta = minetest.get_meta(pos)
local msg
-- temperature
local t0, t1 = meta:get_int("t"), minetest.get_gametime()
local T = meta:get_int("T") -- temperature
if t0 > t1 - machines_minstep then -- activated before natural time
T = T + 1
elseif T > 0 then
if t1 - t0 > machines_timer then -- reset temperature if more than 5s (by default) elapsed since last activation
T = 0; msg = ""
else
T = T - 1
end
end
meta:set_int("t", t1); meta:set_int("T", T)
if T > 2 then -- overheat
minetest.sound_play(basic_machines.sound_overheat, {pos = pos, max_hear_distance = 16, gain = 0.25}, true)
meta:set_string("infotext", S("Overheat! Temperature: @1", T))
return
end
-- protection check
if minetest.is_protected(pos, meta:get_string("owner")) then
meta:set_int("count", 0)
meta:set_int("T", T + 2)
meta:set_string("infotext", S("Protection fail. Reset."))
return
end
-- repeating activation
local iter = meta:get_int("iter")
if iter == 0 then if msg then meta:set_string("infotext", msg) end; return end
local count = 0 -- counts repeats
if iter > 1 then
if basic_machines.properties.no_clock then return end
count = meta:get_int("count")
if reset and count > 0 or count == iter then
meta:set_int("count", 0)
meta:set_int("T", 4)
meta:set_string("infotext", reset_msg or
S("KEYPAD: Resetting. Punch again after @1s to activate.", machines_timer))
return
end
if count < iter - 1 then
minetest.after(machines_timer, function()
basic_machines.use_keypad(pos, machines_TTL)
end)
end
if count < iter then -- this is keypad repeating activation
count = count + 1; meta:set_int("count", count)
count = iter - count
end
end
local text = meta:get_string("text")
if text ~= "" then -- text operations
if text == "@" and meta:get_string("pass") ~= "" then -- keyboard mode, set text from input
text = meta:get_string("input")
meta:set_string("input", "") -- clear input
end
local bit = string_byte(text)
if bit == 36 then -- text starts with $, play sound
local text_sub = text:sub(2)
if text_sub ~= "" then
if iter > 1 then meta:set_int("count", iter) end -- play sound only once
local i = text_sub:find(" ")
if i then
local sound_name = text_sub:sub(1, i - 1)
local pitch = tonumber(text_sub:sub(i + 1)) or 1
if pitch < 0.01 or pitch > 10 then pitch = 1 end
meta:set_string("infotext", S("Playing sound: '@1', pitch: @2", (sound_name:gsub("_", " ")), pitch))
minetest.sound_play({name = sound_name, pitch = pitch}, {pos = pos, max_hear_distance = 16}, true)
else
meta:set_string("infotext", S("Playing sound: '@1'", (text_sub:gsub("_", " "))))
minetest.sound_play(text_sub, {pos = pos, max_hear_distance = 16}, true)
end
return
end
elseif bit == 33 then -- if text starts with !, then we send chat text to all nearby players, radius 5
local text_sub = text:sub(2)
if text_sub ~= "" then
if iter > 1 then meta:set_int("count", iter); count = 0 end -- send text only once
meta:set_string("infotext", S("Keypad operation: @1 cycle left", count))
local math_sqrt = math.sqrt
local tpos = vector.add(pos, {x = meta:get_int("x0"), y = meta:get_int("y0"), z = meta:get_int("z0")})
for _, player in ipairs(minetest.get_connected_players()) do
local pos1 = player:get_pos()
if math_sqrt((pos1.x - tpos.x)^2 + (pos1.y - tpos.y)^2 + (pos1.z - tpos.z)^2) <= 5 then
minetest.chat_send_player(player:get_player_name(), text_sub)
end
end
return
end
end
local tpos = vector.add(pos, {x = meta:get_int("x0"), y = meta:get_int("y0"), z = meta:get_int("z0")})
local node = minetest.get_node_or_nil(tpos); if not node then return end -- error
local name = node.name
if name ~= "basic_machines:keypad" or not vector.equals(tpos, pos) then
if count < 2 then
meta:set_string("infotext", S("Keypad operation: @1 cycle left", count))
else
meta:set_string("infotext", S("Keypad operation: @1 cycles left", count))
end
end
-- set text on target
if signs[name] then -- update text on signs
if use_signs_lib then -- with signs_lib
if signs_lib.update_sign then
signs_lib.update_sign(tpos, {text = text})
end
elseif use_default then -- minetest_game default mod
local infotext
if text:len() > 512 then
infotext = S("KEYPAD: Text too long.")
text = infotext
else
infotext = S('"@1"', text)
end
local tmeta = minetest.get_meta(tpos)
tmeta:set_string("infotext", infotext)
tmeta:set_string("text", text)
end
-- target is keypad, special functions: @, % that output to target keypad's text
elseif name == "basic_machines:keypad" then -- special modify of target keypad's text and change its target
local tmeta = minetest.get_meta(tpos)
if bit == 64 then -- target keypad's text starts with '@' (ascii code 64) -> character replacement
text = text:sub(2)
if text == "" then -- clear target keypad's text
tmeta:set_string("text", ""); return
end
-- read words [j] from blocks above keypad:
local j = 0
text = text:gsub("@", function() -- replace every '@' in text with string on blocks above
j = j + 1; return minetest.get_meta({x = pos.x, y = pos.y + j, z = pos.z}):get_string("infotext")
end, 16) -- up to 16 replacements
if text:len() > 4896 then
tmeta:set_string("text", S("KEYPAD: Text too long."))
else -- set target keypad's text
tmeta:set_string("text", text)
end
elseif bit == 37 then -- target keypad's text starts with '%' (ascii code 37) -> word extraction
local i = tonumber(text:sub(2, 2)) or 0 -- read the number following the '%'
if i > 0 then
-- extract i-th word from text
local ttext = minetest.get_meta({x = pos.x, y = pos.y + 1, z = pos.z}):get_string("infotext")
local j = 0
for word in ttext:gmatch("%S+") do
j = j + 1; if j == i then text = word; break end
end
end
-- set target keypad's text
tmeta:set_string("text", text)
else
-- just set text...
tmeta:set_string("infotext", text)
end
elseif name == "basic_machines:detector" then -- change filter on detector
if bit == 64 then -- if text starts with '@' -> clear the filter
minetest.get_meta(tpos):set_string("node", "")
else
minetest.get_meta(tpos):set_string("node", text)
end
elseif name == "basic_machines:mover" then -- change filter on mover
local tmeta = minetest.get_meta(tpos)
if bit == 64 then -- if text starts with '@' -> clear the filter
tmeta:set_string("prefer", "")
tmeta:get_inventory():set_list("filter", {})
else
local mode = tmeta:get_string("mode")
-- mover input validation
if basic_machines.check_mover_filter(mode, tpos, tmeta, text) then
if mover_no_large_stacks and basic_machines.check_mover_target(mode, tpos, tmeta) then
text = basic_machines.clamp_item_count(text)
end
tmeta:set_string("prefer", text)
tmeta:get_inventory():set_list("filter", {})
end
end
elseif name == "basic_machines:distributor" then
local i = text:find(" ")
if i then
local ti = tonumber(text:sub(1, i - 1)) or 1
local tm = tonumber(text:sub(i + 1)) or 1
if ti >= 1 and ti <= 16 and tm >= -2 and tm <= 2 then
minetest.get_meta(tpos):set_int("active" .. ti, tm)
end
end
elseif name == "basic_machines:autocrafter" then
local tmeta = minetest.get_meta(tpos)
if bit == 64 then -- if text starts with '@' -> clear the recipe
basic_machines.change_autocrafter_recipe(tpos, tmeta:get_inventory(), nil)
else
local i = text:find(" ")
local stack_name, idx
if i then
stack_name = text:sub(1, i - 1)
idx = tonumber(text:sub(i + 1)) or 1
if idx < 1 or idx > 1000 then idx = 1 end
else
stack_name = text
end
if minetest.registered_items[stack_name ~= "" and stack_name or nil] then
basic_machines.change_autocrafter_recipe(tpos, tmeta:get_inventory(), ItemStack(stack_name), idx)
else
tmeta:set_string("infotext", text:gsub("^ +$", ""))
end
end
elseif use_unifieddyes and name:find("basic_machines:light") then
if bit == 105 then -- text starts with 'i' -> set param2
local idx = tonumber(text:sub(2)) or 0
if idx ~= node.param2 and idx % 8 == 0 then -- colorwallmounted palette
node.param2 = idx; minetest.swap_node(tpos, node)
end
else
minetest.get_meta(tpos):set_string("infotext", text:gsub("^ +$", ""))
end
else
minetest.get_meta(tpos):set_string("infotext", text:gsub("^ +$", "")) -- just set text
end
else -- target activation
if count < 2 then
meta:set_string("infotext", S("Keypad operation: @1 cycle left", count))
else
meta:set_string("infotext", S("Keypad operation: @1 cycles left", count))
end
local mode = meta:get_int("mode"); if mode == 0 then return end -- do nothing
local tpos = vector.add(pos, {x = meta:get_int("x0"), y = meta:get_int("y0"), z = meta:get_int("z0")})
local node = minetest.get_node_or_nil(tpos); if not node then return end -- error
local def = minetest.registered_nodes[node.name]
if def and (def.effector or def.mesecons and def.mesecons.effector) then -- activate target
if mode == 3 then -- keypad in toggle mode
local state = meta:get_int("state"); state = 1 - state; meta:set_int("state", state)
if state == 0 then mode = 2 else mode = 1 end
end
local effector = def.effector or def.mesecons.effector
local param = def.effector and ttl or node
-- pass the signal on to target, depending on mode
if mode == 2 and effector.action_on then -- on
effector.action_on(tpos, param) -- run
elseif mode == 1 and effector.action_off then -- off
effector.action_off(tpos, param) -- run
end
end
end
end
minetest.register_node("basic_machines:keypad", {
description = S("Keypad"),
groups = {cracky = 2},
tiles = {"basic_machines_keypad.png"},
sounds = basic_machines.sound_node_machine(),
after_place_node = function(pos, placer)
if not placer then return end
local meta = minetest.get_meta(pos)
meta:set_string("infotext", S("Keypad. Right click to set it up or punch it." ..
" Set any password and text \"@@\" to work as keyboard."))
meta:set_string("owner", placer:get_player_name())
meta:set_int("mode", 2); meta:set_string("pass", "") -- mode, password
meta:set_int("iter", 1); meta:set_int("count", 0) -- repeat, current repeat count
meta:set_string("text", "")
meta:set_int("x0", 0); meta:set_int("y0", 0); meta:set_int("z0", 0) -- target
meta:set_int("input", 0); meta:set_int("state", 0)
meta:set_int("t", 0); meta:set_int("T", 0)
end,
on_rightclick = function(pos, _, player)
local meta, name = minetest.get_meta(pos), player:get_player_name()
local x0, y0, z0 = meta:get_int("x0"), meta:get_int("y0"), meta:get_int("z0")
machines.mark_pos1(name, vector.add(pos, {x = x0, y = y0, z = z0})) -- mark pos1
minetest.show_formspec(name, "basic_machines:keypad_" .. minetest.pos_to_string(pos),
"formspec_version[4]size[6,5.3]no_prepend[]bgcolor[#888888BB;false]set_focus[text]" ..
"field[0.25,0.5;1,0.8;mode;" .. F(S("Mode")) .. ";" .. meta:get_int("mode") ..
"]field[1.5,0.5;1,0.8;iter;" .. F(S("Repeat")) .. ";" .. meta:get_int("iter") ..
"]field[2.75,0.5;3,0.8;pass;" .. F(S("Password")) .. ";" .. meta:get_string("pass") ..
"]field[0.25,3;3.85,0.8;text;" .. F(S("Text")) .. ";" .. F(meta:get_string("text")) ..
"]button[4.1,3;0.5,0.8;sounds;♫]button_exit[4.75,3;1,0.8;OK;" .. F(S("OK")) ..
"]field[0.25,4.25;1,0.8;x0;" .. F(S("Target")) .. ";" .. x0 ..
"]field[1.5,4.25;1,0.8;y0;;" .. y0 .. "]field[2.75,4.25;1,0.8;z0;;" .. z0 ..
"]button[4.75,4.25;1,0.8;help;" .. F(S("help")) .. "]")
end,
on_dig = function(pos, node, digger)
if digger and digger:is_player() then
if minetest.get_meta(pos):get_string("owner") == digger:get_player_name() then -- owner can always remove his keypad
local node_removed = minetest.remove_node(pos)
if node_removed then
minetest.handle_node_drops(pos, {node.name}, digger)
end
return node_removed
end
end
return minetest.node_dig(pos, node, digger)
end,
effector = {
action_on = function(pos, _)
if minetest.get_meta(pos):get_string("pass") == "" then
basic_machines.use_keypad(pos, 1)
end
end,
action_off = function(pos, _)
if minetest.get_meta(pos):get_string("pass") == "" then
basic_machines.use_keypad(pos, 1, true) -- can stop repeats
end
end
}
})
if basic_machines.settings.register_crafts and use_default then
minetest.register_craft({
output = "basic_machines:keypad",
recipe = {
{"default:stick"},
{"default:wood"}
}
})
end