-------------------------------------------------------- -- Minetest :: Advanced Rangefinder Mod v1.0 (finder) -- -- See README.txt for licensing and other information. -- Copyright (c) 2020, Leslie E. Krause -- -- ./games/minetest_game/mods/finder/init.lua -------------------------------------------------------- dofile( minetest.get_modpath( "finder" ) .. "/api.lua" ) local function locator( pos, size, color ) minetest.add_particle( { pos = pos, vel = { x=0, y=0, z=0 }, acc = { x=0, y=0, z=0 }, exptime = 3.0, size = size, collisiondetection = false, vertical = true, texture = "wool_" .. color .. ".png", } ) end local function open_exporter( player_name, results ) local output = "" for i, v in ipairs( results ) do output = output .. string.format( "%s %s at (%0.1f,%0.1f,%0.1f)\n", ( { player = "Player", entity = "Entity" } )[ v.type ] or "Node", v.name, v.pos.x, v.pos.y, v.pos.z ) end local function get_formspec( ) local formspec = "size[6.5,8.5]" .. "textarea[0.5,0.5;6.0,8.0;buffer;Search results;" .. minetest.formspec_escape( output ) .. "]" .. "button_exit[2.0,8.0;2.0,0.3;close;Close]" return formspec end minetest.create_form( nil, player_name, get_formspec( ) ) end local function show_results_viewer( player_name, source, region, radius, results ) local page_idx = 1 local page_size = 25 local function get_formspec( ) local formspec = string.format( "size[6.0,%0.1f]", 1.5 + math.min( #results, page_size ) * 0.4 ) .. "position[0.8,0.5]" .. string.format( "label[0.0,0.0;Found %d %s within %s of radius %d:]", #results, source, region, radius ) local off = 0.0 for idx = ( page_idx - 1 ) * page_size + 1, math.min( page_idx * page_size, #results ) do local data = results[ idx ] if data.type == "player" then formspec = formspec .. string.format( "box[0.0,%0.1f;0.4,0.3;#FFFF00]", 0.6 + off ) .. string.format( "image_button[0.0,%0.1f;0.6,0.4;;show:%s;;false;false]", 0.6 + off, idx ) .. string.format( "label[0.5,%0.2f;Player '%s' at %d meters.]", 0.5 + off, data.name, data.dist ) locator( data.obj:getpos( ), 8.0, "yellow" ) elseif data.type == "entity" then formspec = formspec .. string.format( "box[0.0,%0.1f;0.4,0.3;#00FF00]", 0.6 + off ) .. string.format( "image_button[0.0,%0.1f;0.6,0.4;;show:%s;;false;false]", 0.6 + off, idx ) .. string.format( "label[0.5,%0.2f;Entity '%s' at %d meters.]", 0.5 + off, data.name, data.dist ) locator( data.obj:getpos( ), 8.0, "green" ) else formspec = formspec .. string.format( "box[0.0,%0.1f;0.4,0.3;#00FFFF]", 0.6 + off ) .. string.format( "image_button[0.0,%0.1f;0.6,0.4;;show:%s;;false;false]", 0.6 + off, idx ) .. string.format( "label[0.5,%0.2f;Node '%s' at %d meters.]", 0.5 + off, data.name, data.dist ) locator( vector.offset( data.pos, 0.0, 0.55, 0.0 ), 2.5, "cyan" ) locator( vector.offset( data.pos, 0.0, -0.55, 0.0 ), 2.5, "cyan" ) locator( vector.offset( data.pos, 0.55, 0.0, 0.0 ), 2.5, "cyan" ) locator( vector.offset( data.pos, -0.55, 0.0, 0.0 ), 2.5, "cyan" ) locator( vector.offset( data.pos, 0.5, 0.0, 0.55 ), 2.5, "cyan" ) locator( vector.offset( data.pos, 0.5, 0.0, -0.55 ), 2.5, "cyan" ) end off = off + 0.4 end formspec = formspec .. string.format( "button[0.0,%0.1f;2,0.3;export;Export]", off + 1.0 ) .. string.format( "button[2.0,%0.1f;1,0.3;prev;<<]", off + 1.0 ) .. string.format( "label[3.0,%0.1f;%d of %d]", off + 1.0, page_idx, math.max( math.ceil( #results / page_size ) ) ) .. string.format( "button[4.0,%0.1f;1,0.3;next;>>]", off + 1.0 ) return formspec end local function on_close( meta, player, fields ) if fields.quit then return end if fields.export then open_exporter( player_name, results ) elseif fields.prev then if page_idx > 1 then page_idx = page_idx - 1 minetest.update_form( player_name, get_formspec( ) ) end elseif fields.next then if page_idx < #results / page_size then page_idx = page_idx + 1 minetest.update_form( player_name, get_formspec( ) ) end else local fname = next( fields, nil ) -- use next since we only care about the name of a single button local fval = string.match( fname, "show:(.+)" ) if fval then local data = results[ tonumber( fval ) ] if data.type == "player" then locator( data.obj:getpos( ), 8.0, "yellow" ) elseif data.type == "entity" then locator( data.obj:getpos( ), 8.0, "green" ) else locator( vector.offset( data.pos, 0.0, 0.55, 0.0 ), 2.5, "cyan" ) locator( vector.offset( data.pos, 0.0, -0.55, 0.0 ), 2.5, "cyan" ) locator( vector.offset( data.pos, 0.55, 0.0, 0.0 ), 2.5, "cyan" ) locator( vector.offset( data.pos, -0.55, 0.0, 0.0 ), 2.5, "cyan" ) locator( vector.offset( data.pos, 0.5, 0.0, 0.55 ), 2.5, "cyan" ) locator( vector.offset( data.pos, 0.5, 0.0, -0.55 ), 2.5, "cyan" ) end end end end minetest.create_form( nil, player_name, get_formspec( ), on_close ) end minetest.register_chatcommand( "finder", { description = "Find various nodes or objects inside a sphere or cuboid region.", privs = { server = true }, func = function( player_name, param ) local player = minetest.get_player_by_name( player_name ) local args = string.split( param, " " ) if not minetest.create_form then return false, "This function requires the ActiveFormspecs Mod be installed." end if #args == 0 then return false, "Invalid parameters supplied!" elseif args[ 1 ] == "objects" then local region, radius, player_names, entity_globs, search_logic, has_parent local pos = player:getpos( ) local options = { } local results = { } if #args ~= 7 then return false, "Usage: /finder objects [region] [radius] [player_names] [entity_globs] [search_logic] [has_parent]" end region = ( { sphere = "sphere" } )[ args[ 2 ] ] if region == nil then return false, "Invalid 'region' parameter specified!" end radius = tonumber( args[ 3 ] ) if radius == nil then return false, "Invalid 'radius' parameter specified!" end if args[ 4 ] ~= "nil" and string.find( args[ 4 ], "^{.*}$" ) then player_names = string.split( string.sub( args[ 4 ], 2, -2 ) ) if player_names == nil then return false, "Invalid 'player_names' parameter specified!" end end if args[ 5 ] ~= "nil" and string.find( args[ 5 ], "^{.*}$" ) then entity_globs = string.split( string.sub( args[ 5 ], 2, -2 ) ) if entity_globs == nil then return false, "Invalid 'entity_globs' parameter specified!" end end if args[ 6 ] ~= "nil" then search_logic = ( { any = "any", all = "all" } )[ args[ 6 ] ] if search_logic == nil then return false, "Invalid 'search_logic' parameter specified!" end end if args[ 7 ] ~= "nil" then options.has_parent = ( { ["true"] = true, ["false"] = false } )[ args[ 7 ] ] if options.has_parent == nil then return false, "Invalid 'has_parent' parameter specified!" end end if region == "sphere" then results = minetest.find_objects_in_sphere( pos, radius, player_names, entity_globs, search_logic, options ) elseif region == "cuboid" then results = minetest.find_objects_in_cuboid( pos, radius, radius, player_names, entity_globs, search_logic, options ) end show_results_viewer( player_name, "objects", region, radius, results ) elseif args[ 1 ] == "nodes" then local region, radius, node_globs, search_logic local pos = player:getpos( ) local results = { } if #args ~= 5 then return false, "Usage: /finder nodes [region] [radius] [node_globs] [search_logic]" end region = ( { sphere = "sphere", cuboid = "cuboid" } )[ args[ 2 ] ] if region == nil then return false, "Invalid 'region' parameter specified!" end radius = tonumber( args[ 3 ] ) if radius == nil then return false, "Invalid 'radius' parameter specified!" end if args[ 4 ] ~= "nil" and string.find( args[ 4 ], "^{.*}$" ) then node_globs = string.split( string.sub( args[ 4 ], 2, -2 ) ) if node_globs == nil then return false, "Invalid 'node_globs' parameter specified!" end end if args[ 5 ] ~= "nil" then search_logic = ( { any = "any", all = "all" } )[ args[ 5 ] ] if search_logic == nil then return false, "Invalid 'search_logic' parameter specified!" end end local node_names = minetest.search_registered_nodes( node_globs, search_logic ) if region == "sphere" then results = minetest.find_nodes_in_sphere( pos, radius, node_names ) elseif region == "cuboid" then results = minetest.find_nodes_in_cuboid( pos, radius, radius, node_names ) end show_results_viewer( player_name, "nodes", region, radius, results ) end return true end } )