-- moveBot Turtle Script v1.0 -- Answers chat commands for movement, similar to JokeBot for chat style. -- Uses Movement Library from DefragBot with modifications. -- New: setPosition command, optional automata, redstone-based direction calibration. -- Warp home preserves direction; redstone at South of home helps calibrate. --#region Configuration local CHAT_BOX_PERIPHERAL_NAME = "chatBox" -- Set to your chat box peripheral name if different local COMMAND_PREFIX = "@moveBot" local CHAT_BOT_NAME = "MoveBot" local CHAT_BOT_BRACKETS = "[]" local CHAT_BOT_BRACKET_COLOR = "&b" -- Aqua color for the brackets -- Movement Library Specific Config local AUTOMATA_PERIPHERAL_NAME = "endAutomata" -- Peripheral name for EnduRAutomata or similar local POSITION_FILE = "movebot_pos.json" -- File to save position and direction local REFUEL_SLOT = 16 -- Slot used for refueling local FUEL_ITEM_NAME_PART = "coal" -- Partial name of fuel items (e.g., "coal", "charcoal") local DEBUG_MODE = false -- Set to true for detailed logging local DEBUG_LOG_FILE = "movebot.log" --#endregion --#region Peripherals local chatBox = peripheral.find(CHAT_BOX_PERIPHERAL_NAME) -- `automata` will be initialized by MoveLib's init and stored in the `ml` table or a local within it. -- Redstone API is global (rs.), no specific peripheral wrapping needed here for rs.getInput("front") --#endregion --#region Debug Logger local function logDebug(message, source) if not DEBUG_MODE then return end local logPrefix = source or CHAT_BOT_NAME local logMessage = string.format("[%s] [%s] %s\n", os.date("%Y-%m-%d %H:%M:%S"), logPrefix, message) local file, err = fs.open(DEBUG_LOG_FILE, "a") if file then file.write(logMessage) file.close() else print("DEBUG LOG ERROR: Could not open " .. DEBUG_LOG_FILE .. ": " .. (err or "unknown error")) end end --#endregion --#region Minecraft JSON Text Component Colors local COLORS = { BLACK = "black", DARK_BLUE = "dark_blue", DARK_GREEN = "dark_green", DARK_AQUA = "dark_aqua", DARK_RED = "dark_red", DARK_PURPLE = "dark_purple", GOLD = "gold", GRAY = "gray", DARK_GRAY = "dark_gray", BLUE = "blue", GREEN = "green", AQUA = "aqua", RED = "red", LIGHT_PURPLE = "light_purple", YELLOW = "yellow", WHITE = "white", RESET = "reset" } --#endregion --#region Movement Library (Adapted from DefragBot) -- This library will manage its own state for position and direction. local ml = {} -- Movement Library API table do -- Scope for Movement Library internals local automata_p = nil -- Internal peripheral handle for automata local current_pos = { x = nil, y = nil, z = nil } local current_dir = nil -- 0:N, 1:E, 2:S, 3:W local DIR_VECTORS = { [0] = { x = 0, y = 0, z = 1 }, [1] = { x = 1, y = 0, z = 0 }, [2] = { x = 0, y = 0, z = -1 }, [3] = { x = -1, y = 0, z = 0 } } local DIR_NAMES = { [0] = "North (+Z)", [1] = "East (+X)", [2] = "South (-Z)", [3] = "West (-X)" } local function mlog(message) logDebug(message, "MoveLib") end function ml.savePosition() if current_pos.x == nil or current_pos.y == nil or current_pos.z == nil or current_dir == nil then mlog("Save failed: Position or direction is not fully known.") return end local data = { x = current_pos.x, y = current_pos.y, z = current_pos.z, dir = current_dir } local file, err = fs.open(POSITION_FILE, "w") if file then file.write(textutils.serialiseJSON(data)) file.close() mlog("Position saved: X:" .. data.x .. " Y:" .. data.y .. " Z:" .. data.z .. " Dir:" .. (DIR_NAMES[data.dir] or "Unknown")) else mlog("Error saving position: " .. (err or "unknown error")) end end local function loadPosition() if fs.exists(POSITION_FILE) then local file, err_open = fs.open(POSITION_FILE, "r") if file then local serialized_data = file.readAll() file.close() local success_parse, data = pcall(textutils.unserialiseJSON, serialized_data) if success_parse and type(data) == "table" and data.x ~= nil and data.y ~= nil and data.z ~= nil and data.dir ~= nil then current_pos.x = data.x current_pos.y = data.y current_pos.z = data.z current_dir = data.dir mlog("Position loaded: X:" .. current_pos.x .. " Y:" .. current_pos.y .. " Z:" .. current_pos.z .. " Dir:" .. (DIR_NAMES[current_dir] or "Unknown")) return true else mlog("Failed to parse position file or data invalid. Error: " .. tostring(data)) end else mlog("Error opening position file for reading: " .. (err_open or "unknown error")) end else mlog("Position file ('" .. POSITION_FILE .. "') not found.") end return false end local function dirNameToNumber(name_or_num) if type(name_or_num) == "number" then return name_or_num % 4 end local n = string.lower(tostring(name_or_num)) if n == "n" or n == "north" or n == "0" then return 0 elseif n == "e" or n == "east" or n == "1" then return 1 elseif n == "s" or n == "south" or n == "2" then return 2 elseif n == "w" or n == "west" or n == "3" then return 3 end mlog("Warning: Invalid direction string '" .. n .. "'. Defaulting to North (0).") return 0 -- Default if unrecognized end function ml.turnLeft() if current_dir == nil then mlog("Cannot turn left: Direction unknown."); return false end if turtle.turnLeft() then current_dir = (current_dir - 1 + 4) % 4 mlog("Turned left. New direction: " .. (DIR_NAMES[current_dir] or "Unknown")) ml.savePosition() return true else mlog("Failed to turn left."); return false end end function ml.turnRight() if current_dir == nil then mlog("Cannot turn right: Direction unknown."); return false end if turtle.turnRight() then current_dir = (current_dir + 1) % 4 mlog("Turned right. New direction: " .. (DIR_NAMES[current_dir] or "Unknown")) ml.savePosition() return true else mlog("Failed to turn right."); return false end end function ml.turnAround() if current_dir == nil then mlog("Cannot turn around: Direction unknown."); return false end mlog("Attempting to turn around...") if ml.turnRight() and ml.turnRight() then mlog("Successfully turned around. New direction: " .. (DIR_NAMES[current_dir] or "Unknown")) return true else mlog("Failed to turn around completely."); return false end end function ml.forward() if current_pos.x == nil or current_dir == nil then mlog("Cannot move forward: Position or direction unknown."); return false end if turtle.getFuelLevel() < 1 and turtle.getFuelLimit() ~= 0 then mlog("Cannot move forward: Out of fuel."); return false end if turtle.forward() then local vec = DIR_VECTORS[current_dir] current_pos.x = current_pos.x + vec.x current_pos.y = current_pos.y + vec.y current_pos.z = current_pos.z + vec.z mlog("Moved forward. New position: X:" .. current_pos.x .. " Y:" .. current_pos.y .. " Z:" .. current_pos.z) ml.savePosition() return true else mlog("Failed to move forward (obstacle or other issue)."); return false end end function ml.back() if current_pos.x == nil or current_dir == nil then mlog("Cannot move back: Position or direction unknown."); return false end if turtle.getFuelLevel() < 1 and turtle.getFuelLimit() ~= 0 then mlog("Cannot move backward: Out of fuel."); return false end if turtle.back() then local vec = DIR_VECTORS[current_dir] current_pos.x = current_pos.x - vec.x current_pos.y = current_pos.y - vec.y current_pos.z = current_pos.z - vec.z mlog("Moved back. New position: X:" .. current_pos.x .. " Y:" .. current_pos.y .. " Z:" .. current_pos.z) ml.savePosition() return true else mlog("Failed to move back (obstacle or other issue)."); return false end end function ml.up() if current_pos.y == nil then mlog("Cannot move up: Y position unknown."); return false end if turtle.getFuelLevel() < 1 and turtle.getFuelLimit() ~= 0 then mlog("Cannot move up: Out of fuel."); return false end if turtle.up() then current_pos.y = current_pos.y + 1 mlog("Moved up. New position: X:" .. current_pos.x .. " Y:" .. current_pos.y .. " Z:" .. current_pos.z) ml.savePosition() return true else mlog("Failed to move up (obstacle or other issue)."); return false end end function ml.down() if current_pos.y == nil then mlog("Cannot move down: Y position unknown."); return false end -- No fuel check for down, as it's often free if turtle.down() then current_pos.y = current_pos.y - 1 mlog("Moved down. New position: X:" .. current_pos.x .. " Y:" .. current_pos.y .. " Z:" .. current_pos.z) ml.savePosition() return true else mlog("Failed to move down (obstacle or other issue)."); return false end end function ml.home() if not automata_p then mlog("Cannot go home: Automata peripheral ('" .. AUTOMATA_PERIPHERAL_NAME .. "') not found or not initialized.") return false end mlog("Attempting to warp home...") -- Direction is preserved through warp as per new requirement local success_warp, result_warp = pcall(function() return automata_p.warpToPoint("home") end) if success_warp and result_warp then mlog("Warp home successful.") current_pos.x = 0 current_pos.y = 0 current_pos.z = 0 mlog("Position set to home (0,0,0). Direction remains: " .. (current_dir and DIR_NAMES[current_dir] or "Unknown")) ml.savePosition() -- Save new coords with existing direction (if known) return true else mlog("Failed to warp home: " .. tostring(result_warp or "pcall error")) return false end end function ml.refuel() mlog("Starting refuel process...") turtle.select(REFUEL_SLOT) local items_before_refuel = turtle.getItemCount(REFUEL_SLOT) local item_detail = turtle.getItemDetail(REFUEL_SLOT) local item_name_lower = "" if item_detail and item_detail.name then item_name_lower = string.lower(item_detail.name) end if items_before_refuel > 0 and string.find(item_name_lower, FUEL_ITEM_NAME_PART, 1, true) then mlog("Refueling from slot " .. REFUEL_SLOT .. " (" .. (item_detail.displayName or item_detail.name or "Unknown Fuel") .. " x" .. items_before_refuel .. ")") if turtle.refuel(0) then -- Refuel all local items_after_refuel = turtle.getItemCount(REFUEL_SLOT) local consumed = items_before_refuel - items_after_refuel if consumed > 0 then -- ***** CORRECTED LINE ***** mlog("Refueled successfully. Consumed " .. consumed .. " " .. (item_detail.displayName or item_detail.name or "fuel item(s)") .. ".") else mlog("Refuel command sent, but 0 items consumed. Current Fuel: " .. turtle.getFuelLevel()) end else mlog("turtle.refuel(0) command failed. Current Fuel: " .. turtle.getFuelLevel()) end elseif items_before_refuel > 0 then mlog("Item in fuel slot " .. REFUEL_SLOT .. " is not recognized fuel: " .. (item_detail.displayName or item_detail.name or "Unknown Item")) else mlog("Fuel slot " .. REFUEL_SLOT .. " is empty.") end -- Replenish fuel slot local space_in_slot = turtle.getItemSpace(REFUEL_SLOT) local sucked_count = 0 if space_in_slot > 0 then mlog("Attempting to suck " .. space_in_slot .. " items to replenish fuel slot...") for _ = 1, space_in_slot do if turtle.getItemCount(REFUEL_SLOT) >= 64 then break end -- Slot full local current_item_detail = turtle.getItemDetail(REFUEL_SLOT) if current_item_detail and current_item_detail.name and (not string.find(string.lower(current_item_detail.name), FUEL_ITEM_NAME_PART, 1, true)) and turtle.getItemCount(REFUEL_SLOT) > 0 then mlog("Slot " .. REFUEL_SLOT .. " now contains non-fuel (" .. (current_item_detail.displayName or current_item_detail.name) .. "). Stopping suck.") break end if turtle.suck() then sucked_count = sucked_count + 1 sleep(0.1) -- Small delay for items to transfer else mlog("Failed to suck item or chest empty after " .. sucked_count .. " items.") break end end if sucked_count > 0 then mlog("Sucked " .. sucked_count .. " items to replenish fuel slot.") end else mlog("Fuel slot full or contains non-fuel item, no replenishment attempted.") end mlog("Refuel process finished. Current Fuel: " .. turtle.getFuelLevel()) return true -- refuel always "succeeds" in trying end function ml.setPos(x, y, z, dir_input) if type(x) ~= "number" or type(y) ~= "number" or type(z) ~= "number" then mlog("Error setting position: x, y, z must be numbers.") return false end current_pos.x = x current_pos.y = y current_pos.z = z current_dir = dirNameToNumber(dir_input) mlog("Position manually set to: X:" .. x .. " Y:" .. y .. " Z:" .. z .. " Dir:" .. (DIR_NAMES[current_dir] or "Unknown")) ml.savePosition() return true end local function turnToDir(target_dir_num) if current_dir == nil then mlog("Cannot turn to direction: Current direction unknown."); return false end if current_dir == target_dir_num then return true end mlog("Turning to face " .. (DIR_NAMES[target_dir_num] or "Unknown Target Dir") .. " from " .. (DIR_NAMES[current_dir] or "Unknown Current Dir")) local diff = (target_dir_num - current_dir + 4) % 4 if diff == 1 then return ml.turnRight() elseif diff == 2 then return ml.turnAround() elseif diff == 3 then return ml.turnLeft() end return false -- Should not happen if diff is 1,2,3 end ml.turnToDir = turnToDir -- Expose for external use if needed, though moveTo uses it internally function ml.moveTo(target_x, target_y, target_z, target_dir_input) if current_pos.x == nil or current_dir == nil then mlog("Cannot moveTo: Current position or direction unknown."); return false end local target_dir_num = dirNameToNumber(target_dir_input) mlog(string.format("Attempting to move to: X:%s Y:%s Z:%s Dir:%s (Num: %d)", tostring(target_x), tostring(target_y), tostring(target_z), (DIR_NAMES[target_dir_num] or "Unknown"), target_dir_num)) local attempts = 0 local max_attempts = 200 -- Increased attempts for complex moves local max_stuck_per_axis = 3 local stuck_counter = 0 while (current_pos.x ~= target_x or current_pos.y ~= target_y or current_pos.z ~= target_z) and attempts < max_attempts do attempts = attempts + 1 local moved_this_iteration = false -- Y-axis movement (priority to clear height differences) if current_pos.y < target_y then if ml.up() then moved_this_iteration = true; stuck_counter = 0 else stuck_counter = stuck_counter + 1; mlog("moveTo: Blocked moving UP ("..stuck_counter..")") end elseif current_pos.y > target_y then if ml.down() then moved_this_iteration = true; stuck_counter = 0 else stuck_counter = stuck_counter + 1; mlog("moveTo: Blocked moving DOWN ("..stuck_counter..")") end end if moved_this_iteration then goto continue_loop end -- X-axis movement if current_pos.x < target_x then if turnToDir(1) then -- East if ml.forward() then moved_this_iteration = true; stuck_counter = 0 else stuck_counter = stuck_counter + 1; mlog("moveTo: Blocked moving EAST (+X) ("..stuck_counter..")") end end elseif current_pos.x > target_x then if turnToDir(3) then -- West if ml.forward() then moved_this_iteration = true; stuck_counter = 0 else stuck_counter = stuck_counter + 1; mlog("moveTo: Blocked moving WEST (-X) ("..stuck_counter..")") end end end if moved_this_iteration then goto continue_loop end -- Z-axis movement if current_pos.z < target_z then -- Target is +Z relative to turtle if turnToDir(0) then -- North if ml.forward() then moved_this_iteration = true; stuck_counter = 0 else stuck_counter = stuck_counter + 1; mlog("moveTo: Blocked moving NORTH (+Z) ("..stuck_counter..")") end end elseif current_pos.z > target_z then -- Target is -Z relative to turtle if turnToDir(2) then -- South if ml.forward() then moved_this_iteration = true; stuck_counter = 0 else stuck_counter = stuck_counter + 1; mlog("moveTo: Blocked moving SOUTH (-Z) ("..stuck_counter..")") end end end ::continue_loop:: if not moved_this_iteration then mlog("moveTo: No successful move in iteration " .. attempts .. ". Stuck counter: " .. stuck_counter) if stuck_counter >= max_stuck_per_axis then mlog("moveTo: Stuck for " .. stuck_counter .. " iterations on an axis. Aborting moveTo.") break end end sleep(0.05) -- Small delay between actions end if attempts >= max_attempts then mlog("moveTo: Reached max attempts (" .. max_attempts .. ").") end -- Final turn to target direction if not turnToDir(target_dir_num) then mlog("moveTo: Failed to make final turn to target direction.") end local success = current_pos.x == target_x and current_pos.y == target_y and current_pos.z == target_z and current_dir == target_dir_num if success then mlog("moveTo: Successfully reached target.") else mlog(string.format("moveTo: Finished. Final Pos: X:%s Y:%s Z:%s Dir:%s. Target was X:%s Y:%s Z:%s Dir:%s", tostring(current_pos.x), tostring(current_pos.y), tostring(current_pos.z), (DIR_NAMES[current_dir] or "Unk"), tostring(target_x), tostring(target_y), tostring(target_z), (DIR_NAMES[target_dir_num] or "Unk"))) end return success end function ml.init() mlog("Initializing MoveLib for MoveBot...") automata_p = peripheral.find(AUTOMATA_PERIPHERAL_NAME) if not automata_p then mlog("WARNING: Automata peripheral ('" .. AUTOMATA_PERIPHERAL_NAME .. "') not found. 'gohome' command will be unavailable.") else mlog("Automata peripheral ('" .. AUTOMATA_PERIPHERAL_NAME .. "') found.") end if not loadPosition() then mlog("Position data not found or corrupt from '" .. POSITION_FILE .. "'.") if automata_p then mlog("Attempting to warp home to set initial coordinates (0,0,0). Direction will be preserved (likely unknown if first run).") if ml.home() then -- home() sets current_pos to 0,0,0 if successful. current_dir is NOT changed by warp. mlog("Warped home successfully. Coordinates are 0,0,0. Direction may still be unknown if not previously set.") -- If current_dir was nil, savePosition in home() wouldn't save. This is fine. else mlog("CRITICAL: Failed initial home warp. Position and direction remain unknown.") current_pos = { x = nil, y = nil, z = nil } -- Ensure they are nil current_dir = nil end else mlog("CRITICAL: No Automata available and no saved position. Position and direction are unknown.") current_pos = { x = nil, y = nil, z = nil } current_dir = nil end else mlog("Position loaded successfully from '" .. POSITION_FILE .. "'.") end if current_pos.x == nil or current_dir == nil then mlog("MoveLib initialized, BUT POSITION OR DIRECTION IS UNKNOWN.") mlog("Use '" .. COMMAND_PREFIX .. " setpos ' to set manually.") mlog("If at home (0,0,0), you can also try '" .. COMMAND_PREFIX .. " calibratedir'.") else mlog("MoveLib initialized. Pos: X:" .. current_pos.x .. " Y:" .. current_pos.y .. " Z:" .. current_pos.z .. " Dir:" .. (DIR_NAMES[current_dir] or "Invalid")) end end function ml.getPosition() return current_pos end function ml.getDirection() return current_dir end function ml.getDirectionName() if current_dir == nil then return "Unknown" end return DIR_NAMES[current_dir] or "Invalid (" .. tostring(current_dir) .. ")" end -- Expose shorthand versions if desired, e.g., ml.l = ml.turnLeft ml.l = ml.turnLeft; ml.r = ml.turnRight; ml.f = ml.forward; ml.b = ml.back; ml.u = ml.up; ml.d = ml.down; ml.a = ml.turnAround; ml.h = ml.home; ml.m = ml.moveTo; end -- End of Movement Library scope --#endregion --#region Helper Functions (Chat) local function sendFormattedChat(messageComponents, recipientUsername) logDebug("Attempting to send formatted chat. Recipient: " .. (recipientUsername or "ALL"), "ChatHelper") if not chatBox then local plainText = "" for _, comp in ipairs(messageComponents) do plainText = plainText .. (comp.text or "") end local noChatMsg = "[" .. CHAT_BOT_NAME .. "-NoChatBox" .. (recipientUsername and (" to " .. recipientUsername) or "") .. "] " .. plainText print(noChatMsg) logDebug("ChatBox not found. Printed to console: " .. noChatMsg, "ChatHelper") return end local jsonMessage = textutils.serialiseJSON(messageComponents) if not jsonMessage then local fallbackMsg = "Error: Could not serialize message for formatted sending." logDebug("JSON Serialization Error. Fallback: " .. fallbackMsg, "ChatHelper") if recipientUsername then chatBox.sendMessageToPlayer(fallbackMsg, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR) else chatBox.sendMessage(fallbackMsg, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR) end return end local success, err if recipientUsername then success, err = pcall(chatBox.sendFormattedMessageToPlayer, jsonMessage, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR) else success, err = pcall(chatBox.sendFormattedMessage, jsonMessage, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR) end if not success then logDebug("Error sending formatted message: " .. (err or "Unknown error") .. ". Attempting fallback to plain text.", "ChatHelper") local plainText = "" for _, comp in ipairs(messageComponents) do plainText = plainText .. (comp.text or "") end if recipientUsername then chatBox.sendMessageToPlayer(plainText, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR) else chatBox.sendMessage(plainText, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR) end else logDebug("Formatted message sent successfully.", "ChatHelper") end os.sleep(0.3) -- Prevent chat spamming issues end local function announce(messageComponents) sendFormattedChat(messageComponents) end --#endregion --#region Command Handlers local commandHandlers = {} commandHandlers.help = function(username, _) logDebug("Executing command: help, User: " .. username) announce({{text = "--- MoveBot Commands (" .. COMMAND_PREFIX .. ") ---", color = COLORS.GOLD, bold = true}}) announce({{text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = " - Shows this help message.", color = COLORS.GRAY}}) announce({{text = COMMAND_PREFIX .. " pos", color = COLORS.AQUA}, {text = " - Shows current position and direction.", color = COLORS.GRAY}}) announce({{text = COMMAND_PREFIX .. " fuel", color = COLORS.AQUA}, {text = " - Shows current fuel level.", color = COLORS.GRAY}}) announce({{text = COMMAND_PREFIX .. " setpos ", color = COLORS.AQUA}, {text = " - Sets current position and direction (N,E,S,W or 0-3).", color = COLORS.GRAY}}) announce({{text = COMMAND_PREFIX .. " calibratedir", color = COLORS.AQUA}, {text = " - Calibrates direction to South if at home (0,0,0) and facing South (redstone signal).", color = COLORS.GRAY}}) announce({{text = COMMAND_PREFIX .. " f / b / l / r / u / d / around", color = COLORS.AQUA}, {text = " - Basic movements.", color = COLORS.GRAY}}) announce({{text = COMMAND_PREFIX .. " gohome", color = COLORS.AQUA}, {text = " - Warps to (0,0,0) via Automata (if available). Direction preserved.", color = COLORS.GRAY}}) announce({{text = COMMAND_PREFIX .. " refuel", color = COLORS.AQUA}, {text = " - Attempts to refuel from slot " .. REFUEL_SLOT .. " and replenish it.", color = COLORS.GRAY}}) announce({{text = COMMAND_PREFIX .. " moveto ", color = COLORS.AQUA}, {text = " - Moves to specified coordinates and direction.", color = COLORS.GRAY}}) announce({{text = COMMAND_PREFIX .. " mult ", color = COLORS.AQUA}, {text = " - Executes a sequence of basic moves (l,r,f,b,u,d,a). Ex: lffr", color = COLORS.GRAY}}) end commandHandlers.pos = function(username, _) logDebug("Executing command: pos, User: " .. username) local p = ml.getPosition() local d = ml.getDirectionName() if p.x ~= nil and p.y ~= nil and p.z ~= nil then announce({{text = "Current Position: ", color = COLORS.AQUA}, {text = "X:" .. p.x .. " Y:" .. p.y .. " Z:" .. p.z, color = COLORS.WHITE}}) announce({{text = "Current Direction: ", color = COLORS.AQUA}, {text = d, color = COLORS.WHITE}}) else announce({{text = "Position or direction is currently unknown. Use '", color = COLORS.YELLOW}, {text=COMMAND_PREFIX .. " setpos", color=COLORS.AQUA}, {text="' or '", color=COLORS.YELLOW},{text=COMMAND_PREFIX .. " calibratedir", color=COLORS.AQUA},{text="'.", color=COLORS.YELLOW}}) end end commandHandlers.fuel = function(username, _) logDebug("Executing command: fuel, User: " .. username) local level = turtle.getFuelLevel() local limit = turtle.getFuelLimit() announce({{text = "Fuel Level: ", color = COLORS.AQUA}, {text = level .. (limit == 0 and " (Unlimited)" or " / " .. limit), color = COLORS.WHITE}}) end commandHandlers.setpos = function(username, args) logDebug("Executing command: setpos, User: " .. username .. ", Args: " .. textutils.serialize(args)) if #args ~= 4 then announce({{text = "Usage: ", color = COLORS.RED}, {text = COMMAND_PREFIX .. " setpos ", color = COLORS.AQUA}}) announce({{text = "Example: ", color = COLORS.GRAY}, {text = COMMAND_PREFIX .. " setpos 0 0 0 N", color = COLORS.WHITE}}) return end local x, y, z = tonumber(args[1]), tonumber(args[2]), tonumber(args[3]) local dir_input = args[4] if not x or not y or not z then announce({{text = "Error: X, Y, Z coordinates must be numbers.", color = COLORS.RED}}) return end if ml.setPos(x, y, z, dir_input) then announce({{text = "Position and direction set successfully.", color = COLORS.GREEN}}) else announce({{text = "Failed to set position (check logs for details).", color = COLORS.RED}}) end end commandHandlers.calibratedir = function(username, _) logDebug("Executing command: calibratedir, User: " .. username) local p = ml.getPosition() if not (p.x == 0 and p.y == 0 and p.z == 0) then announce({{text = "Error: Must be at home (0,0,0) to use calibratedir this way.", color = COLORS.RED}}) announce({{text = "Current position: X:"..tostring(p.x).." Y:"..tostring(p.y).." Z:"..tostring(p.z), color = COLORS.YELLOW}}) return end logDebug("Checking redstone input on 'front' for South calibration.") local is_facing_south = false local rs_success, rs_value_or_err = pcall(redstone.getInput, "front") if not rs_success then announce({{text = "Error accessing redstone API: ", color = COLORS.RED}, {text=tostring(rs_value_or_err), color=COLORS.YELLOW}}) logDebug("Redstone API pcall failed: " .. tostring(rs_value_or_err)) return end is_facing_south = rs_value_or_err if is_facing_south then local old_dir_name = ml.getDirectionName() ml.setPos(p.x, p.y, p.z, 2) -- 2 is South announce({{text = "Redstone signal on front detected. Direction calibrated to South.", color = COLORS.GREEN}}) logDebug("Calibrated direction to South. Was: " .. old_dir_name) else announce({{text = "No redstone signal on front. Direction not changed.", color = COLORS.YELLOW}}) announce({{text = "Current direction: ", color = COLORS.AQUA}, {text = ml.getDirectionName(), color = COLORS.WHITE}}) logDebug("Redstone front input false. Direction remains: " .. ml.getDirectionName()) end end local simpleMoveCommands = { f = ml.forward, b = ml.back, l = ml.turnLeft, r = ml.turnRight, u = ml.up, d = ml.down, around = ml.turnAround } for cmd, func in pairs(simpleMoveCommands) do commandHandlers[cmd] = function(username, _) logDebug("Executing simple move: " .. cmd .. ", User: " .. username) if func() then announce({{text = "Move '" .. cmd .. "' successful.", color = COLORS.GREEN}}) else announce({{text = "Move '" .. cmd .. "' failed. (Obstacle, unknown state, or no fuel?)", color = COLORS.RED}}) end end end commandHandlers.gohome = function(username, _) logDebug("Executing command: gohome, User: " .. username) if ml.home() then announce({{text = "Successfully warped home to (0,0,0). Direction preserved.", color = COLORS.GREEN}}) else announce({{text = "Failed to warp home. (Automata not found or warp error).", color = COLORS.RED}}) end end commandHandlers.refuel = function(username, _) logDebug("Executing command: refuel, User: " .. username) ml.refuel() -- refuel function logs verbosely announce({{text = "Refuel attempt finished. Current fuel: " .. turtle.getFuelLevel(), color = COLORS.GREEN}}) end commandHandlers.moveto = function(username, args) logDebug("Executing command: moveto, User: " .. username .. ", Args: " .. textutils.serialize(args)) if #args ~= 4 then announce({{text = "Usage: ", color = COLORS.RED}, {text = COMMAND_PREFIX .. " moveto ", color = COLORS.AQUA}}) return end local x, y, z = tonumber(args[1]), tonumber(args[2]), tonumber(args[3]) local dir_input = args[4] if not x or not y or not z then announce({{text = "Error: X, Y, Z coordinates must be numbers.", color = COLORS.RED}}) return end announce({{text = "Attempting to move to X:"..x.." Y:"..y.." Z:"..z.." Dir:"..dir_input.."...", color = COLORS.YELLOW}}) if ml.moveTo(x, y, z, dir_input) then announce({{text = "Successfully moved to target.", color = COLORS.GREEN}}) else announce({{text = "Failed to reach target or make final turn.", color = COLORS.RED}}) end end commandHandlers.mult = function(username, args) logDebug("Executing command: mult, User: " .. username .. ", Args: " .. textutils.serialize(args)) if #args ~= 1 or type(args[1]) ~= "string" then announce({{text = "Usage: ", color = COLORS.YELLOW}, {text = COMMAND_PREFIX .. " mult ", color = COLORS.AQUA}}) announce({{text = "Sequence uses single letters: l,r,f,b,u,d,a. Example: lffr", color = COLORS.GRAY}}) return end local sequence = string.lower(args[1]) announce({{text = "Executing sequence: ", color = COLORS.AQUA}, {text = sequence, color = COLORS.WHITE}}) local success = true for i = 1, #sequence do local move_char = string.sub(sequence, i, i) local move_func = simpleMoveCommands[move_char] or (move_char == 'a' and ml.turnAround) if move_func then announce({{text = "Executing '", color = COLORS.GRAY}, {text = move_char, color = COLORS.YELLOW}, {text = "'...", color = COLORS.GRAY}}) if not move_func() then announce({{text = "Move '", color = COLORS.RED}, {text = move_char, color = COLORS.YELLOW}, {text = "' failed. Stopping sequence.", color = COLORS.RED}}) if turtle.getFuelLevel() < 10 and turtle.getFuelLimit() ~= 0 then announce({{text="CRITICAL: Fuel low ("..turtle.getFuelLevel()..")!",c=COLORS.DARK_RED,b=true}}); end success = false break end sleep(0.2) -- Small delay between moves in sequence else announce({{text = "Unknown character '", color = COLORS.RED}, {text = move_char, color = COLORS.YELLOW}, {text = "' in sequence. Stopping.", color = COLORS.RED}}) success = false break end end if success then announce({{text = "Sequence finished.", color = COLORS.GREEN}}) end end --#endregion --#region Main Loop local function run() term.clear(); term.setCursorPos(1, 1) if DEBUG_MODE then local file, err = fs.open(DEBUG_LOG_FILE, "w") -- Clear log on start if file then file.write(string.format("[%s] [%s] Script Initializing - DEBUG MODE ENABLED (v1.0)\n", os.date("%Y-%m-%d %H:%M:%S"), CHAT_BOT_NAME)) file.write("======================================================================\n") file.close() else print("DEBUG LOG ERROR: Could not clear/initialize " .. DEBUG_LOG_FILE .. ": " .. (err or "unknown error")) end end logDebug("Script run() started.") if not chatBox then logDebug("WARNING: Chat Box peripheral ('" .. CHAT_BOX_PERIPHERAL_NAME .. "') not found! Chat features will be printed to console only.") print("WARNING: Chat Box ('" .. CHAT_BOX_PERIPHERAL_NAME .. "') not found! Chat features disabled/printed to console.") end -- Initialize Movement Library ml.init() -- This will log its own status, including if position is unknown logDebug("Announcing online status.") print(CHAT_BOT_NAME .. " script started. Type '" .. COMMAND_PREFIX .. " help' in chat or '@all'.") if chatBox then announce({{text = CHAT_BOT_NAME .. " online!", color = COLORS.GREEN, bold = true}, {text = " Ready for movement commands.", color = COLORS.GRAY}}) announce({{text = "Type '", color = COLORS.GRAY}, {text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = "' or '@all' for commands.", color = COLORS.GRAY}}) if ml.getPosition().x == nil or ml.getDirection() == nil then announce({{text = "WARNING: My position or direction is unknown. Please use '", color = COLORS.YELLOW, bold=true}, {text = COMMAND_PREFIX.." setpos", color=COLORS.AQUA, bold=true}, {text = "' or '", color = COLORS.YELLOW, bold=true}, {text = COMMAND_PREFIX.." calibratedir", color=COLORS.AQUA, bold=true}, {text = "' to initialize me.", color = COLORS.YELLOW, bold=true}}) end end while true do local eventData = {os.pullEvent()} local eventType = eventData[1] logDebug("Event received: " .. eventType .. " - Data: " .. textutils.serialize(eventData, {compact = true, max_depth = 2}), "EventLoop") if eventType == "chat" then local eUsername, eMessage, _, eIsHidden = eventData[2], eventData[3], eventData[4], eventData[5] if not eIsHidden and eMessage then if string.lower(eMessage) == "@all" then logDebug("@all command received from " .. eUsername) -- Green and Magenta for @all response announce({{text = "Hi @all! For " .. CHAT_BOT_NAME .. " commands, type: ", color = COLORS.GREEN}, {text = COMMAND_PREFIX .. " help", color = COLORS.LIGHT_PURPLE}}) elseif string.sub(eMessage, 1, #COMMAND_PREFIX) == COMMAND_PREFIX then logDebug("Chat command received from " .. eUsername .. ": " .. eMessage) local parts = {} for part in string.gmatch(eMessage, "[^%s]+") do table.insert(parts, part) end local commandName = "" if parts[2] then commandName = string.lower(parts[2]) end local cmdArgs = {} for i = 3, #parts do table.insert(cmdArgs, parts[i]) end logDebug("Parsed command: '" .. commandName .. "', Args: " .. textutils.serialize(cmdArgs)) if commandHandlers[commandName] then local success_pcall, err_pcall = pcall(commandHandlers[commandName], eUsername, cmdArgs) if not success_pcall then logDebug("Error executing command '" .. commandName .. "': " .. tostring(err_pcall)) announce({{text = "Oops! Something went wrong while processing your command: ", color = COLORS.RED}, {text = commandName, color = COLORS.YELLOW}}) announce({{text = "Error details: ", color = COLORS.RED}, {text = tostring(err_pcall), color = COLORS.YELLOW}}) end elseif commandName ~= "" then announce({{text = "Unknown command: '", color = COLORS.RED}, {text = commandName, color = COLORS.YELLOW}, {text = "'. Try '", color = COLORS.RED}, {text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = "'.", color = COLORS.RED}}) end end end elseif eventType == "terminate" then logDebug("Terminate event received. Shutting down.") if chatBox then announce({{text = CHAT_BOT_NAME .. " shutting down...", color = COLORS.YELLOW, bold = true}}) end logDebug("Script terminated.") print(CHAT_BOT_NAME .. " terminated.") return end end end run() --#endregion