Files
lwcomponents/detector.lua
2022-02-19 14:08:26 +10:00

828 lines
18 KiB
Lua

local utils = ...
local S = utils.S
if utils.digilines_supported or utils.mesecon_supported then
local detect_interval = 0.5
local function mesecons_on (pos)
local meta = minetest.get_meta (pos)
if meta then
if meta:get_int ("power_on") == 0 then
utils.mesecon_receptor_on (pos, utils.mesecon_default_rules)
meta:set_int ("power_on", 1)
end
end
end
local function mesecons_off (pos)
local meta = minetest.get_meta (pos)
if meta then
if meta:get_int ("power_on") ~= 0 then
utils.mesecon_receptor_off (pos, utils.mesecon_default_rules)
meta:set_int ("power_on", 0)
end
end
end
local function to_relative_coords (pos, testpos)
local base = { x = testpos.x - pos.x,
y = testpos.y - pos.y,
z = testpos.z - pos.z }
local node = minetest.get_node (pos)
if node then
if node.param2 == 3 then -- +x
return { x = (base.z * -1), y = base.y, z = base.x }
elseif node.param2 == 0 then -- -z
return { x = (base.x * -1), y = base.y, z = (base.z * -1) }
elseif node.param2 == 1 then -- -x
return { x = base.z, y = base.y, z = (base.x * -1) }
elseif node.param2 == 2 then -- +z
return { x = base.x, y = base.y, z = base.z }
end
end
return { x = 0, y = 0, z = 0 }
end
local function send_detect_message (pos, detected_list)
if utils.digilines_supported then
local meta = minetest.get_meta (pos)
if meta then
local channel = meta:get_string ("channel")
if channel:len () > 0 then
utils.digilines_receptor_send (pos,
utils.digilines_default_rules,
channel,
{ action = "detect",
detected = detected_list })
end
end
end
end
local function filter_item (pos, mode, testpos)
local base = { x = math.floor (testpos.x - pos.x + 0.5),
y = math.floor (testpos.y - pos.y + 0.5),
z = math.floor (testpos.z - pos.z + 0.5) }
if base.x == 0 and base.y == 0 and base.z == 0 then
return false
end
if mode == 1 then
-- all
return true
elseif mode == 2 then
-- forward
local node = minetest.get_node (pos)
if node then
if node.param2 == 0 then
-- -z
return (base.x == 0 and base.y == 0 and base.z < 0)
elseif node.param2 == 1 then
-- -x
return (base.x < 0 and base.y == 0 and base.z == 0)
elseif node.param2 == 2 then
-- +z
return (base.x == 0 and base.y == 0 and base.z > 0)
elseif node.param2 == 3 then
-- +x
return (base.x > 0 and base.y == 0 and base.z == 0)
end
end
elseif mode == 3 then
-- up
return (base.x == 0 and base.z == 0 and base.y > 0)
elseif mode == 4 then
-- down
return (base.x == 0 and base.z == 0 and base.y < 0)
end
return false
end
local function get_entity_height (objref)
if objref.get_luaentity then
local entity = objref:get_luaentity ()
if entity and entity.name then
def = minetest.registered_entities[entity.name]
if def and type (def.collisionbox) == "table" and
type (def.collisionbox[5]) == "number" then
return def.collisionbox[5]
end
end
end
local props = objref:get_properties ()
if props and props.collisionbox and type (props.collisionbox) == "table" and
type (props.collisionbox[5]) == "number" then
return props.collisionbox[5]
end
return 0
end
local function detect (pos)
local meta = minetest.get_meta (pos)
local detected = false
if meta then
local radius = meta:get_int ("radius")
local mode = meta:get_int ("mode")
local object = minetest.get_objects_inside_radius (pos, radius + 0.5)
local detected_list = { }
for i = 1, #object, 1 do
if object[i]:is_player () then
-- player
if meta:get_string ("players") == "true" and
filter_item (pos, mode, object[i]:get_pos ()) then
detected_list[#detected_list + 1] =
{
type = "player",
name = object[i]:get_player_name (),
label = object[i]:get_player_name (),
pos = to_relative_coords (pos, object[i]:get_pos ()),
count = 1,
hp = object[i]:get_hp (),
height = get_entity_height (object[i])
}
detected = true
end
elseif object[i].get_luaentity and object[i]:get_luaentity () and
object[i]:get_luaentity ().name and
object[i]:get_luaentity ().name == "__builtin:item" then
-- drop
if meta:get_string ("drops") == "true" and
filter_item (pos, mode, object[i]:get_pos ()) then
local stack = ItemStack (object[i]:get_luaentity ().itemstring or "")
if stack and not stack:is_empty () then
detected_list[#detected_list + 1] =
{
type = "drop",
name = stack:get_name (),
label = stack:get_name (),
pos = to_relative_coords (pos, object[i]:get_pos ()),
count = stack:get_count (),
hp = 0,
height = 0
}
detected = true
end
end
elseif object[i].get_pos and object[i]:get_pos () then
-- entity
if meta:get_string ("entities") == "true" and
filter_item (pos, mode, object[i]:get_pos ()) then
local name = object[i]:get_nametag_attributes ()
local label = ""
if type (name) == "table" then
label = tostring (name.text or "")
end
name = (object[i].get_luaentity and
object[i]:get_luaentity () and
object[i]:get_luaentity ().name) or ""
detected_list[#detected_list + 1] =
{
type = "entity",
name = name,
label = label,
pos = to_relative_coords (pos, object[i]:get_pos ()),
count = 1,
hp = object[i]:get_hp (),
height = get_entity_height (object[i])
}
detected = true
end
end
end
if meta:get_string ("nodes") == "true" then
for y = (pos.y - radius), (pos.y + radius) do
for x = (pos.x - radius), (pos.x + radius) do
for z = (pos.z - radius), (pos.z + radius) do
local testpos = { x = x, y = y, z = z }
local node = minetest.get_node (testpos)
if node and node.name ~= "air" and node.name ~= "ignore" and
filter_item (pos, mode, testpos) then
detected_list[#detected_list + 1] =
{
type = "node",
name = node.name,
label = node.name,
pos = testpos,
count = 1,
hp = 0,
height = 0
}
detected = true
end
end
end
end
end
if detected then
mesecons_on (pos)
send_detect_message (pos, detected_list)
else
mesecons_off (pos)
end
end
end
local function get_form_spec (is_off, radius, entities, players, drops, nodes, mode)
return
"formspec_version[3]\n"..
"size[11.75,9.0;true]\n"..
"field[1.0,1.0;4.0,0.8;channel;Channel;${channel}]\n"..
"button[5.5,1.0;2.0,0.8;setchannel;Set]\n"..
"button[8.25,1.0;2.5,0.8;"..((is_off and "start;Start") or "stop;Stop").."]\n"..
"field[1.0,2.5;4.0,0.8;radius;Radius;"..tostring (radius).."]\n"..
"button[5.5,2.5;2.0,0.8;setradius;Set]\n"..
"checkbox[1.0,4.4;entities;Entities;"..entities.."]\n"..
"checkbox[1.0,5.4;players;Players;"..players.."]\n"..
"checkbox[1.0,6.4;drops;Drops;"..drops.."]\n"..
"checkbox[1.0,7.4;nodes;Nodes;"..nodes.."]\n"..
"textlist[4.875,4.0;5.875,4.0;mode;All,Forward,Up,Down;"..tostring (mode)..";false]"
end
local function update_form_spec (pos)
local node = minetest.get_node (pos)
local meta = minetest.get_meta (pos)
if node and meta then
local is_off = node.name == "lwcomponents:detector" or
node.name == "lwcomponents:detector_locked"
meta:set_string ("formspec",
get_form_spec (is_off,
meta:get_int ("radius"),
meta:get_string ("entities"),
meta:get_string ("players"),
meta:get_string ("drops"),
meta:get_string ("nodes"),
meta:get_int ("mode")))
end
end
local function start_detector (pos)
local node = minetest.get_node (pos)
local meta = minetest.get_meta (pos)
if node and meta then
if node.name == "lwcomponents:detector" then
local meta = minetest.get_meta (pos)
if meta then
node.name = "lwcomponents:detector_on"
minetest.swap_node (pos, node)
minetest.get_node_timer (pos):start (detect_interval)
update_form_spec (pos)
end
elseif node.name == "lwcomponents:detector_locked" then
local meta = minetest.get_meta (pos)
if meta then
node.name = "lwcomponents:detector_locked_on"
minetest.swap_node (pos, node)
minetest.get_node_timer (pos):start (detect_interval)
update_form_spec (pos)
end
end
end
end
local function stop_detector (pos)
local node = minetest.get_node (pos)
local meta = minetest.get_meta (pos)
if node and meta then
if node.name == "lwcomponents:detector_on" then
local meta = minetest.get_meta (pos)
if meta then
node.name = "lwcomponents:detector"
minetest.swap_node (pos, node)
minetest.get_node_timer (pos):stop ()
mesecons_off (pos)
update_form_spec (pos)
end
elseif node.name == "lwcomponents:detector_locked_on" then
local meta = minetest.get_meta (pos)
if meta then
node.name = "lwcomponents:detector_locked"
minetest.swap_node (pos, node)
minetest.get_node_timer (pos):stop ()
mesecons_off (pos)
update_form_spec (pos)
end
end
end
end
local function on_destruct (pos)
minetest.get_node_timer (pos):stop ()
mesecons_off (pos)
end
local function after_place_node (pos, placer, itemstack, pointed_thing)
local meta = minetest.get_meta (pos)
local is_off = itemstack and (itemstack:get_name () == "lwcomponents:detector" or
itemstack:get_name () == "lwcomponents:detector_locked")
meta:set_string ("inventory", "{ main = { }, filter = { } }")
meta:set_string ("formspec", get_form_spec (is_off, 1, 0, 0, 0, 0, 1))
meta:set_string ("entities", "false")
meta:set_string ("players", "false")
meta:set_string ("drops", "false")
meta:set_string ("nodes", "false")
meta:set_int ("mode", 1)
meta:set_int ("radius", 1)
meta:set_int ("power_on", 0)
-- If return true no item is taken from itemstack
return false
end
local function after_place_node_locked (pos, placer, itemstack, pointed_thing)
after_place_node (pos, placer, itemstack, pointed_thing)
if placer and placer:is_player () then
local meta = minetest.get_meta (pos)
meta:set_string ("owner", placer:get_player_name ())
meta:set_string ("infotext", "Detector (owned by "..placer:get_player_name ()..")")
end
-- If return true no item is taken from itemstack
return false
end
local function on_receive_fields (pos, formname, fields, sender)
if not utils.can_interact_with_node (pos, sender) then
return
end
if fields.setchannel then
local meta = minetest.get_meta (pos)
if meta then
meta:set_string ("channel", fields.channel)
end
end
if fields.setradius then
local meta = minetest.get_meta (pos)
if meta then
local radius = math.min (math.max (tonumber (fields.radius) or 1, 1), 5)
meta:set_int ("radius", radius)
update_form_spec (pos)
end
end
if fields.start then
start_detector (pos)
end
if fields.stop then
stop_detector (pos)
end
if fields.entities ~= nil then
local meta = minetest.get_meta (pos)
if meta then
meta:set_string ("entities", fields.entities)
update_form_spec (pos)
end
end
if fields.players ~= nil then
local meta = minetest.get_meta (pos)
if meta then
meta:set_string ("players", fields.players)
update_form_spec (pos)
end
end
if fields.drops ~= nil then
local meta = minetest.get_meta (pos)
if meta then
meta:set_string ("drops", fields.drops)
update_form_spec (pos)
end
end
if fields.nodes ~= nil then
local meta = minetest.get_meta (pos)
if meta then
meta:set_string ("nodes", fields.nodes)
update_form_spec (pos)
end
end
if fields.mode then
local event = minetest.explode_textlist_event (fields.mode)
if event.type == "CHG" then
local meta = minetest.get_meta (pos)
if meta then
meta:set_int ("mode", event.index)
update_form_spec (pos)
end
end
end
end
local function can_dig (pos, player)
if not utils.can_interact_with_node (pos, player) then
return false
end
return true
end
local function on_blast (pos, intensity)
local meta = minetest.get_meta (pos)
if meta then
if intensity >= 1.0 then
on_destruct (pos)
minetest.remove_node (pos)
else -- intensity < 1.0
local node = minetest.get_node_or_nil (pos)
if node then
local items = minetest.get_node_drops (node, nil)
if items and #items > 0 then
local stack = ItemStack (items[1])
if stack then
preserve_metadata (pos, node, meta, { stack })
utils.item_drop (stack, nil, pos)
on_destruct (pos)
minetest.remove_node (pos)
end
end
end
end
end
end
local function on_timer (pos, elapsed)
detect (pos)
return true
end
local function on_rightclick (pos, node, clicker, itemstack, pointed_thing)
if not utils.can_interact_with_node (pos, clicker) then
if clicker and clicker:is_player () then
local owner = "<unknown>"
local meta = minetest.get_meta (pos)
if meta then
owner = meta:get_string ("owner")
end
local spec =
"formspec_version[3]"..
"size[8.0,4.0,false]"..
"label[1.0,1.0;Owned by "..minetest.formspec_escape (owner).."]"..
"button_exit[3.0,2.0;2.0,1.0;close;Close]"
minetest.show_formspec (clicker:get_player_name (),
"lwcomponents:component_privately_owned",
spec)
end
end
return itemstack
end
local function digilines_support ()
if utils.digilines_supported then
return
{
wire =
{
rules = utils.digilines_default_rules,
},
effector =
{
action = function (pos, node, channel, msg)
local meta = minetest.get_meta(pos)
if meta then
local this_channel = meta:get_string ("channel")
if this_channel ~= "" and this_channel == channel and
type (msg) == "string" then
local m = { }
for w in string.gmatch(msg, "[^%s]+") do
m[#m + 1] = w
end
if m[1] == "start" then
start_detector (pos)
elseif m[1] == "stop" then
stop_detector (pos)
elseif m[1] == "radius" then
local radius = math.min (math.max (tonumber (m[2] or 1) or 1, 1), 5)
meta:set_int ("radius", radius)
update_form_spec (pos)
elseif m[1] == "entities" then
meta:set_string ("entities", ((m[2] == "true") and "true") or "false")
update_form_spec (pos)
elseif m[1] == "players" then
meta:set_string ("players", ((m[2] == "true") and "true") or "false")
update_form_spec (pos)
elseif m[1] == "drops" then
meta:set_string ("drops", ((m[2] == "true") and "true") or "false")
update_form_spec (pos)
elseif m[1] == "nodes" then
meta:set_string ("nodes", ((m[2] == "true") and "true") or "false")
update_form_spec (pos)
elseif m[1] == "mode" then
if m[2] == "all" then
meta:set_int ("mode", 1)
update_form_spec (pos)
elseif m[2] == "forward" then
meta:set_int ("mode", 2)
update_form_spec (pos)
elseif m[2] == "up" then
meta:set_int ("mode", 3)
update_form_spec (pos)
elseif m[2] == "down" then
meta:set_int ("mode", 4)
update_form_spec (pos)
end
end
end
end
end,
}
}
end
return nil
end
local function mesecon_support ()
if utils.mesecon_supported then
return
{
receptor =
{
state = utils.mesecon_state_off,
rules = utils.mesecon_default_rules
}
}
end
return nil
end
minetest.register_node("lwcomponents:detector", {
description = S("Detector"),
tiles = { "lwdetector_face.png", "lwdetector_face.png", "lwdetector.png",
"lwdetector.png", "lwdetector.png", "lwdetector_face.png"},
is_ground_content = false,
groups = { cracky = 3 },
sounds = default.node_sound_stone_defaults (),
paramtype = "none",
param1 = 0,
paramtype2 = "facedir",
param2 = 1,
floodable = false,
drop = "lwcomponents:detector",
_digistuff_channelcopier_fieldname = "channel",
mesecons = mesecon_support (),
digiline = digilines_support (),
on_destruct = on_destruct,
on_receive_fields = on_receive_fields,
can_dig = can_dig,
after_place_node = after_place_node,
on_blast = on_blast,
on_timer = on_timer,
on_rightclick = on_rightclick
})
minetest.register_node("lwcomponents:detector_locked", {
description = S("Detector (locked)"),
tiles = { "lwdetector_face.png", "lwdetector_face.png", "lwdetector.png",
"lwdetector.png", "lwdetector.png", "lwdetector_face.png"},
is_ground_content = false,
groups = { cracky = 3 },
sounds = default.node_sound_stone_defaults (),
paramtype = "none",
param1 = 0,
paramtype2 = "facedir",
param2 = 1,
floodable = false,
drop = "lwcomponents:detector_locked",
_digistuff_channelcopier_fieldname = "channel",
mesecons = mesecon_support (),
digiline = digilines_support (),
on_destruct = on_destruct,
on_receive_fields = on_receive_fields,
can_dig = can_dig,
after_place_node = after_place_node_locked,
on_blast = on_blast,
on_timer = on_timer,
on_rightclick = on_rightclick
})
minetest.register_node("lwcomponents:detector_on", {
description = S("Detector"),
tiles = { "lwdetector_face_on.png", "lwdetector_face_on.png", "lwdetector.png",
"lwdetector.png", "lwdetector.png", "lwdetector_face_on.png"},
is_ground_content = false,
groups = { cracky = 3, not_in_creative_inventory = 1 },
sounds = default.node_sound_stone_defaults (),
paramtype = "none",
param1 = 0,
paramtype2 = "facedir",
param2 = 1,
floodable = false,
drop = "lwcomponents:detector",
_digistuff_channelcopier_fieldname = "channel",
mesecons = mesecon_support (),
digiline = digilines_support (),
on_destruct = on_destruct,
on_receive_fields = on_receive_fields,
can_dig = can_dig,
after_place_node = after_place_node,
on_blast = on_blast,
on_timer = on_timer,
on_rightclick = on_rightclick
})
minetest.register_node("lwcomponents:detector_locked_on", {
description = S("Detector (locked)"),
tiles = { "lwdetector_face_on.png", "lwdetector_face_on.png", "lwdetector.png",
"lwdetector.png", "lwdetector.png", "lwdetector_face_on.png"},
is_ground_content = false,
groups = { cracky = 3, not_in_creative_inventory = 1 },
sounds = default.node_sound_stone_defaults (),
paramtype = "none",
param1 = 0,
paramtype2 = "facedir",
param2 = 1,
floodable = false,
drop = "lwcomponents:detector_locked",
_digistuff_channelcopier_fieldname = "channel",
mesecons = mesecon_support (),
digiline = digilines_support (),
on_destruct = on_destruct,
on_receive_fields = on_receive_fields,
can_dig = can_dig,
after_place_node = after_place_node_locked,
on_blast = on_blast,
on_timer = on_timer,
on_rightclick = on_rightclick
})
end -- utils.digilines_supported or utils.mesecon_supported
--