--local bc = better_commands local S = minetest.get_translator(minetest.get_current_modname()) better_commands.execute_subcommands = { ---Aligns relative to certain axes ---@param branches contextTable[] ---@param index integer ---@return boolean ---@return string? ---@nodiscard align = function(branches, index) local branch_data = branches[index] local param = branches.param[branch_data.i+1] if not param then return false, better_commands.error(S("Missing argument for subcommand @1", "align")) end local axes = {param[3]:match("^([xyz])([xyz]?)([xyz]?)$")} if not axes[1] then return false, better_commands.error(S("Invalid swizzle, expected combination of 'x', 'y', and 'z'")) end for _ ,axis in pairs(axes) do branch_data.pos[axis] = math.floor(branch_data.pos[axis]) end branch_data.i = branch_data.i + 2 return true end, --[[ ---Sets anchor to feet or eyes ---@param branches contextTable[] ---@param index integer ---@return boolean ---@return string? ---@nodiscard anchored = function(branches, index) local branch_data = branches[index] local param = branches.param[branch_data.i+1] if not param then return false, better_commands.error(S("Missing argument for subcommand @1", "anchored")) end local anchor = tostring(param[3]):lower() if anchor == "feet" or anchor == "eyes" then branch_data.anchor = anchor else return false, better_commands.error(S("Invalid entity anchor position @1", anchor)) end branch_data.i = branch_data.i + 2 return true end, --]] ---Changes executor ---@param branches contextTable[] ---@param index integer ---@return boolean|string ---@return string? ---@nodiscard as = function(branches, index) local branch_data = branches[index] local param = branches.param[branch_data.i+1] if not param then return false, better_commands.error(S("Missing argument for subcommand @1", "as")) end if param.type ~= "selector" then return false, better_commands.error(S("Invalid target: @1", table.concat(param, "", 3))) end local targets, err = better_commands.parse_selector(param, branch_data) if err or not targets then return false, err end if #targets > 1 then for _, target in ipairs(targets) do local new_branch = table.copy(branch_data) new_branch.executor = target new_branch.i = new_branch.i + 2 table.insert(branches, new_branch) end return "branched" elseif #targets == 1 then branch_data.executor = targets[1] branch_data.i = branch_data.i + 2 return true else return "notarget" end end, ---Changes position ---@param branches contextTable[] ---@param index integer ---@return boolean|string ---@return string? ---@nodiscard at = function(branches, index) local branch_data = branches[index] local param = branches.param[branch_data.i+1] if not param then return false, better_commands.error(S("Missing argument for subcommand @1", "at")) end if param.type ~= "selector" then return false, better_commands.error(S("Invalid target: @1", table.concat(param, "", 3))) end local targets, err = better_commands.parse_selector(param, branch_data) if err or not targets then return false, err end if #targets > 1 then for _, target in ipairs(targets) do local new_branch = table.copy(branch_data) new_branch.pos = target.get_pos and target:get_pos() or target branch_data.rot = better_commands.get_entity_rotation(target) or branch_data.rot new_branch.i = new_branch.i + 2 table.insert(branches, new_branch) end return "branched" elseif #targets == 1 then branch_data.pos = targets[1].get_pos and targets[1]:get_pos() or targets[1] branch_data.rot = better_commands.get_entity_rotation(targets[1]) or branch_data.rot branch_data.i = branch_data.i + 2 return true else return "notarget" end end, ---Changes rotation ---@param branches contextTable[] ---@param index integer ---@return boolean|string ---@return string? ---@nodiscard facing = function(branches, index) local branch_data = branches[index] local split_param = branches.param local i = branch_data.i if split_param[i+1] then if split_param[i+1][3] == "entity" and split_param[i+2] then local targets, err = better_commands.parse_selector(split_param[i+2], branch_data) if err or not targets then return false, err end if #targets > 1 then for _, target in ipairs(targets) do local target_pos = target.get_pos and target:get_pos() or target local new_branch = table.copy(branch_data) ---@diagnostic disable-next-line: param-type-mismatch new_branch.rot = better_commands.point_at_pos(branch_data.executor, target_pos) new_branch.i = branch_data.i + 3 end return "branched" elseif #targets == 1 then local target_pos = targets[1].get_pos and targets[1]:get_pos() or targets[1] ---@diagnostic disable-next-line: param-type-mismatch branch_data.rot = better_commands.point_at_pos(branch_data.executor, target_pos) branch_data.i = branch_data.i + 3 return true else return "notarget" end else local target_pos, err = better_commands.parse_pos(split_param, i+1, branch_data) if err then return false, err end ---@diagnostic disable-next-line: param-type-mismatch branch_data.rot = better_commands.point_at_pos(branch_data.executor, target_pos) branch_data.i = branch_data.i + 4 return true end end return true end, ---Changes position ---@param branches contextTable[] ---@param index integer ---@return boolean|string ---@return string? ---@nodiscard positioned = function(branches, index) local branch_data = branches[index] local param = branches.param[branch_data.i+1] if not param then return false, better_commands.error(S("Missing argument for subcommand @1", "positioned")) end if param[3] == "as" then local selector = branches.param[branch_data.i+2] if not selector or selector.type ~= "selector" then return false, better_commands.error(S("Invalid argument for @1", "positioned")) end local targets, err = better_commands.parse_selector(selector, branch_data) if err or not targets then return false, err end if #targets > 1 then for _, target in ipairs(targets) do local new_branch = table.copy(branch_data) branch_data.pos = target.get_pos and target:get_pos() or target new_branch.i = new_branch.i + 3 table.insert(branches, new_branch) end return "branched" elseif #targets == 1 then branch_data.pos = targets[1].get_pos and targets[1]:get_pos() or targets[1] branch_data.i = branch_data.i + 3 return true else return "notarget" end else local pos, err = better_commands.parse_pos(branches.param, branch_data.i+1, branch_data) if err then return false, err end branch_data.pos = pos --branch_data.anchor = "feet" branch_data.i = branch_data.i + 4 return true end end, ---Changes rotation ---@param branches contextTable[] ---@param index integer ---@return boolean|string ---@return string? ---@nodiscard rotated = function(branches, index) local branch_data = branches[index] local param = branches.param[branch_data.i+1] if not param then return false, better_commands.error(S("Missing argument for subcommand @1", "rotated")) end if param[3] == "as" then local selector = branches.param[branch_data.i+2] if not selector or selector.type ~= "selector" then return false, better_commands.error(S("Invalid argument for rotated")) end local targets, err = better_commands.parse_selector(selector, branch_data) if err or not targets then return false, err end if #targets > 1 then for _, target in ipairs(targets) do local new_branch = table.copy(branch_data) branch_data.rot = better_commands.get_entity_rotation(target) or branch_data.rot new_branch.i = new_branch.i + 3 table.insert(branches, new_branch) end return "branched" elseif #targets == 1 then branch_data.rot = better_commands.get_entity_rotation(targets[1]) or branch_data.rot branch_data.i = branch_data.i + 3 return true else return "notarget" end else if not (branches.param[branch_data.i+1] and branches.param[branch_data.i+2]) then return false, better_commands.error(S("Missing argument(s)) for rotated")) end local victim_rot = branch_data.rot if branches.param[branch_data.i+1].type == "number" then victim_rot.y = math.rad(tonumber(branches.param[branch_data.i+1][3]) or 0) elseif branches.param[branch_data.i+1].type == "relative" then victim_rot.y = victim_rot.y+math.rad(tonumber(branches.param[branch_data.i+1][3]:sub(2,-1)) or 0) else return false, better_commands.error(S("Invalid argument for rotated")) end if branches.param[branch_data.i+2].type == "number" then victim_rot.x = math.rad(tonumber(branches.param[branch_data.i+2][3]) or 0) elseif branches.param[branch_data.i+2].type == "relative" then victim_rot.x = victim_rot.x+math.rad(tonumber(branches.param[branch_data.i+2][3]:sub(2,-1)) or 0) else return false, better_commands.error(S("Invalid argument for rotated")) end branch_data.rot = victim_rot branch_data.i = branch_data.i + 3 return true end end, ---Runs a command ---@param branches contextTable[] ---@param index integer ---@return boolean|string ---@return string? ---@nodiscard run = function(branches, index) local branch_data = branches[index] if not ( branch_data.executor and branch_data.executor.get_pos and branch_data.pos and type(branch_data.pos) == "table" ) then return "notarget" end if not branches.param[branch_data.i+1] then return false, better_commands.error(S("Missing command")) end local command, command_param command, command_param = branch_data.original_command:match( "%/?([%S]+)%s*(.-)$", branches.param[branch_data.i+1][1] ) while command == "bc" do branch_data.i = branch_data.i + 1 command, command_param = branch_data.original_command:match( "%/?([%S]+)%s*(.-)$", branches.param[branch_data.i+1][1] ) end if command == "execute" then branch_data.i = branch_data.i + 2 return true end local def = better_commands.commands[command] if def and command ~= "old" and (branch_data.command_block or minetest.check_player_privs(branch_data.origin, def.privs)) then return "done", def.real_func(branch_data.origin, command_param, table.copy(branch_data)) else return false, better_commands.error(S("Invalid command or privs: @1", command)) end end } better_commands.register_command("execute", { params = S(" ..."), --params = S(" ..."), description = S("Run any Better Command (not other commands) after changing the context"), -- Requires more privileges since it can literally run any command privs = {server = true, ban = true, privs = true}, func = function(name, param, context) local split_param = better_commands.parse_params(param) if not split_param[1] then return false, nil, 0 end local branch = 1 local branches = {param = split_param} branches[1] = table.copy(context) branches[1].i = 1 branches[1].original_command = param local success_count = 0 while true do -- for each branch: local status, message, command_output, count while true do -- for each subcommand: local cmd_index = branches[branch].i if cmd_index > #split_param then break end local subcmd = split_param[cmd_index][3] if better_commands.execute_subcommands[subcmd] then status, message, command_output, count = better_commands.execute_subcommands[subcmd](branches, branch) if not status then return status, message, 0 end if status == "branched" or status == "notarget" or status == "done" then break end else return false, better_commands.error(S("Invalid subcommand: @1", subcmd)), 0 end end if status == "done" then success_count = success_count + (message and 1 or 0) -- "message" is status when done if command_output then minetest.chat_send_player(name, command_output) end end if branch >= #branches then break else branch = branch + 1 end end return true, S("Successfully executed @1 times", success_count), success_count end })