Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- 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 <x> <y> <z> <dir>' 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 <x> <y> <z> <dir>", 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 <x> <y> <z> <dir>", color = COLORS.AQUA}, {text = " - Moves to specified coordinates and direction.", color = COLORS.GRAY}})
- announce({{text = COMMAND_PREFIX .. " mult <sequence>", 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 <x> <y> <z> <direction>", 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 <x> <y> <z> <direction>", 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 <sequence>", 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
Add Comment
Please, Sign In to add comment