Myros27

moveBot

May 20th, 2025 (edited)
25
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 39.56 KB | None | 0 0
  1. -- moveBot Turtle Script v1.0
  2. -- Answers chat commands for movement, similar to JokeBot for chat style.
  3. -- Uses Movement Library from DefragBot with modifications.
  4. -- New: setPosition command, optional automata, redstone-based direction calibration.
  5. -- Warp home preserves direction; redstone at South of home helps calibrate.
  6.  
  7. --#region Configuration
  8. local CHAT_BOX_PERIPHERAL_NAME = "chatBox" -- Set to your chat box peripheral name if different
  9.  
  10. local COMMAND_PREFIX = "@moveBot"
  11. local CHAT_BOT_NAME = "MoveBot"
  12. local CHAT_BOT_BRACKETS = "[]"
  13. local CHAT_BOT_BRACKET_COLOR = "&b" -- Aqua color for the brackets
  14.  
  15. -- Movement Library Specific Config
  16. local AUTOMATA_PERIPHERAL_NAME = "endAutomata" -- Peripheral name for EnduRAutomata or similar
  17. local POSITION_FILE = "movebot_pos.json"       -- File to save position and direction
  18. local REFUEL_SLOT = 16                         -- Slot used for refueling
  19. local FUEL_ITEM_NAME_PART = "coal"             -- Partial name of fuel items (e.g., "coal", "charcoal")
  20.  
  21. local DEBUG_MODE = false -- Set to true for detailed logging
  22. local DEBUG_LOG_FILE = "movebot.log"
  23. --#endregion
  24.  
  25. --#region Peripherals
  26. local chatBox = peripheral.find(CHAT_BOX_PERIPHERAL_NAME)
  27. -- `automata` will be initialized by MoveLib's init and stored in the `ml` table or a local within it.
  28. -- Redstone API is global (rs.), no specific peripheral wrapping needed here for rs.getInput("front")
  29. --#endregion
  30.  
  31. --#region Debug Logger
  32. local function logDebug(message, source)
  33.     if not DEBUG_MODE then return end
  34.     local logPrefix = source or CHAT_BOT_NAME
  35.     local logMessage = string.format("[%s] [%s] %s\n", os.date("%Y-%m-%d %H:%M:%S"), logPrefix, message)
  36.     local file, err = fs.open(DEBUG_LOG_FILE, "a")
  37.     if file then
  38.         file.write(logMessage)
  39.         file.close()
  40.     else
  41.         print("DEBUG LOG ERROR: Could not open " .. DEBUG_LOG_FILE .. ": " .. (err or "unknown error"))
  42.     end
  43. end
  44. --#endregion
  45.  
  46. --#region Minecraft JSON Text Component Colors
  47. local COLORS = {
  48.     BLACK = "black", DARK_BLUE = "dark_blue", DARK_GREEN = "dark_green", DARK_AQUA = "dark_aqua",
  49.     DARK_RED = "dark_red", DARK_PURPLE = "dark_purple", GOLD = "gold", GRAY = "gray",
  50.     DARK_GRAY = "dark_gray", BLUE = "blue", GREEN = "green", AQUA = "aqua", RED = "red",
  51.     LIGHT_PURPLE = "light_purple", YELLOW = "yellow", WHITE = "white", RESET = "reset"
  52. }
  53. --#endregion
  54.  
  55. --#region Movement Library (Adapted from DefragBot)
  56. -- This library will manage its own state for position and direction.
  57. local ml = {} -- Movement Library API table
  58.  
  59. do -- Scope for Movement Library internals
  60.     local automata_p = nil -- Internal peripheral handle for automata
  61.     local current_pos = { x = nil, y = nil, z = nil }
  62.     local current_dir = nil -- 0:N, 1:E, 2:S, 3:W
  63.  
  64.     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 } }
  65.     local DIR_NAMES = { [0] = "North (+Z)", [1] = "East (+X)", [2] = "South (-Z)", [3] = "West (-X)" }
  66.  
  67.     local function mlog(message) logDebug(message, "MoveLib") end
  68.  
  69.     function ml.savePosition()
  70.         if current_pos.x == nil or current_pos.y == nil or current_pos.z == nil or current_dir == nil then
  71.             mlog("Save failed: Position or direction is not fully known.")
  72.             return
  73.         end
  74.         local data = { x = current_pos.x, y = current_pos.y, z = current_pos.z, dir = current_dir }
  75.         local file, err = fs.open(POSITION_FILE, "w")
  76.         if file then
  77.             file.write(textutils.serialiseJSON(data))
  78.             file.close()
  79.             mlog("Position saved: X:" .. data.x .. " Y:" .. data.y .. " Z:" .. data.z .. " Dir:" .. (DIR_NAMES[data.dir] or "Unknown"))
  80.         else
  81.             mlog("Error saving position: " .. (err or "unknown error"))
  82.         end
  83.     end
  84.  
  85.     local function loadPosition()
  86.         if fs.exists(POSITION_FILE) then
  87.             local file, err_open = fs.open(POSITION_FILE, "r")
  88.             if file then
  89.                 local serialized_data = file.readAll()
  90.                 file.close()
  91.                 local success_parse, data = pcall(textutils.unserialiseJSON, serialized_data)
  92.                 if success_parse and type(data) == "table" and data.x ~= nil and data.y ~= nil and data.z ~= nil and data.dir ~= nil then
  93.                     current_pos.x = data.x
  94.                     current_pos.y = data.y
  95.                     current_pos.z = data.z
  96.                     current_dir = data.dir
  97.                     mlog("Position loaded: X:" .. current_pos.x .. " Y:" .. current_pos.y .. " Z:" .. current_pos.z .. " Dir:" .. (DIR_NAMES[current_dir] or "Unknown"))
  98.                     return true
  99.                 else
  100.                     mlog("Failed to parse position file or data invalid. Error: " .. tostring(data))
  101.                 end
  102.             else
  103.                 mlog("Error opening position file for reading: " .. (err_open or "unknown error"))
  104.             end
  105.         else
  106.             mlog("Position file ('" .. POSITION_FILE .. "') not found.")
  107.         end
  108.         return false
  109.     end
  110.  
  111.     local function dirNameToNumber(name_or_num)
  112.         if type(name_or_num) == "number" then
  113.             return name_or_num % 4
  114.         end
  115.         local n = string.lower(tostring(name_or_num))
  116.         if n == "n" or n == "north" or n == "0" then return 0
  117.         elseif n == "e" or n == "east" or n == "1" then return 1
  118.         elseif n == "s" or n == "south" or n == "2" then return 2
  119.         elseif n == "w" or n == "west" or n == "3" then return 3
  120.         end
  121.         mlog("Warning: Invalid direction string '" .. n .. "'. Defaulting to North (0).")
  122.         return 0 -- Default if unrecognized
  123.     end
  124.  
  125.     function ml.turnLeft()
  126.         if current_dir == nil then mlog("Cannot turn left: Direction unknown."); return false end
  127.         if turtle.turnLeft() then
  128.             current_dir = (current_dir - 1 + 4) % 4
  129.             mlog("Turned left. New direction: " .. (DIR_NAMES[current_dir] or "Unknown"))
  130.             ml.savePosition()
  131.             return true
  132.         else mlog("Failed to turn left."); return false end
  133.     end
  134.  
  135.     function ml.turnRight()
  136.         if current_dir == nil then mlog("Cannot turn right: Direction unknown."); return false end
  137.         if turtle.turnRight() then
  138.             current_dir = (current_dir + 1) % 4
  139.             mlog("Turned right. New direction: " .. (DIR_NAMES[current_dir] or "Unknown"))
  140.             ml.savePosition()
  141.             return true
  142.         else mlog("Failed to turn right."); return false end
  143.     end
  144.  
  145.     function ml.turnAround()
  146.         if current_dir == nil then mlog("Cannot turn around: Direction unknown."); return false end
  147.         mlog("Attempting to turn around...")
  148.         if ml.turnRight() and ml.turnRight() then
  149.             mlog("Successfully turned around. New direction: " .. (DIR_NAMES[current_dir] or "Unknown"))
  150.             return true
  151.         else mlog("Failed to turn around completely."); return false end
  152.     end
  153.  
  154.     function ml.forward()
  155.         if current_pos.x == nil or current_dir == nil then mlog("Cannot move forward: Position or direction unknown."); return false end
  156.         if turtle.getFuelLevel() < 1 and turtle.getFuelLimit() ~= 0 then mlog("Cannot move forward: Out of fuel."); return false end
  157.         if turtle.forward() then
  158.             local vec = DIR_VECTORS[current_dir]
  159.             current_pos.x = current_pos.x + vec.x
  160.             current_pos.y = current_pos.y + vec.y
  161.             current_pos.z = current_pos.z + vec.z
  162.             mlog("Moved forward. New position: X:" .. current_pos.x .. " Y:" .. current_pos.y .. " Z:" .. current_pos.z)
  163.             ml.savePosition()
  164.             return true
  165.         else mlog("Failed to move forward (obstacle or other issue)."); return false end
  166.     end
  167.  
  168.     function ml.back()
  169.         if current_pos.x == nil or current_dir == nil then mlog("Cannot move back: Position or direction unknown."); return false end
  170.         if turtle.getFuelLevel() < 1 and turtle.getFuelLimit() ~= 0 then mlog("Cannot move backward: Out of fuel."); return false end
  171.         if turtle.back() then
  172.             local vec = DIR_VECTORS[current_dir]
  173.             current_pos.x = current_pos.x - vec.x
  174.             current_pos.y = current_pos.y - vec.y
  175.             current_pos.z = current_pos.z - vec.z
  176.             mlog("Moved back. New position: X:" .. current_pos.x .. " Y:" .. current_pos.y .. " Z:" .. current_pos.z)
  177.             ml.savePosition()
  178.             return true
  179.         else mlog("Failed to move back (obstacle or other issue)."); return false end
  180.     end
  181.  
  182.     function ml.up()
  183.         if current_pos.y == nil then mlog("Cannot move up: Y position unknown."); return false end
  184.         if turtle.getFuelLevel() < 1 and turtle.getFuelLimit() ~= 0 then mlog("Cannot move up: Out of fuel."); return false end
  185.         if turtle.up() then
  186.             current_pos.y = current_pos.y + 1
  187.             mlog("Moved up. New position: X:" .. current_pos.x .. " Y:" .. current_pos.y .. " Z:" .. current_pos.z)
  188.             ml.savePosition()
  189.             return true
  190.         else mlog("Failed to move up (obstacle or other issue)."); return false end
  191.     end
  192.  
  193.     function ml.down()
  194.         if current_pos.y == nil then mlog("Cannot move down: Y position unknown."); return false end
  195.         -- No fuel check for down, as it's often free
  196.         if turtle.down() then
  197.             current_pos.y = current_pos.y - 1
  198.             mlog("Moved down. New position: X:" .. current_pos.x .. " Y:" .. current_pos.y .. " Z:" .. current_pos.z)
  199.             ml.savePosition()
  200.             return true
  201.         else mlog("Failed to move down (obstacle or other issue)."); return false end
  202.     end
  203.  
  204.     function ml.home()
  205.         if not automata_p then
  206.             mlog("Cannot go home: Automata peripheral ('" .. AUTOMATA_PERIPHERAL_NAME .. "') not found or not initialized.")
  207.             return false
  208.         end
  209.         mlog("Attempting to warp home...")
  210.         -- Direction is preserved through warp as per new requirement
  211.         local success_warp, result_warp = pcall(function() return automata_p.warpToPoint("home") end)
  212.         if success_warp and result_warp then
  213.             mlog("Warp home successful.")
  214.             current_pos.x = 0
  215.             current_pos.y = 0
  216.             current_pos.z = 0
  217.             mlog("Position set to home (0,0,0). Direction remains: " .. (current_dir and DIR_NAMES[current_dir] or "Unknown"))
  218.             ml.savePosition() -- Save new coords with existing direction (if known)
  219.             return true
  220.         else
  221.             mlog("Failed to warp home: " .. tostring(result_warp or "pcall error"))
  222.             return false
  223.         end
  224.     end
  225.  
  226.     function ml.refuel()
  227.         mlog("Starting refuel process...")
  228.         turtle.select(REFUEL_SLOT)
  229.         local items_before_refuel = turtle.getItemCount(REFUEL_SLOT)
  230.         local item_detail = turtle.getItemDetail(REFUEL_SLOT)
  231.         local item_name_lower = ""
  232.         if item_detail and item_detail.name then item_name_lower = string.lower(item_detail.name) end
  233.  
  234.         if items_before_refuel > 0 and string.find(item_name_lower, FUEL_ITEM_NAME_PART, 1, true) then
  235.             mlog("Refueling from slot " .. REFUEL_SLOT .. " (" .. (item_detail.displayName or item_detail.name or "Unknown Fuel") .. " x" .. items_before_refuel .. ")")
  236.             if turtle.refuel(0) then -- Refuel all
  237.                 local items_after_refuel = turtle.getItemCount(REFUEL_SLOT)
  238.                 local consumed = items_before_refuel - items_after_refuel
  239.                 if consumed > 0 then
  240.                     -- ***** CORRECTED LINE *****
  241.                     mlog("Refueled successfully. Consumed " .. consumed .. " " .. (item_detail.displayName or item_detail.name or "fuel item(s)") .. ".")
  242.                 else
  243.                     mlog("Refuel command sent, but 0 items consumed. Current Fuel: " .. turtle.getFuelLevel())
  244.                 end
  245.             else
  246.                 mlog("turtle.refuel(0) command failed. Current Fuel: " .. turtle.getFuelLevel())
  247.             end
  248.         elseif items_before_refuel > 0 then
  249.             mlog("Item in fuel slot " .. REFUEL_SLOT .. " is not recognized fuel: " .. (item_detail.displayName or item_detail.name or "Unknown Item"))
  250.         else
  251.             mlog("Fuel slot " .. REFUEL_SLOT .. " is empty.")
  252.         end
  253.  
  254.         -- Replenish fuel slot
  255.         local space_in_slot = turtle.getItemSpace(REFUEL_SLOT)
  256.         local sucked_count = 0
  257.         if space_in_slot > 0 then
  258.             mlog("Attempting to suck " .. space_in_slot .. " items to replenish fuel slot...")
  259.             for _ = 1, space_in_slot do
  260.                 if turtle.getItemCount(REFUEL_SLOT) >= 64 then break end -- Slot full
  261.                 local current_item_detail = turtle.getItemDetail(REFUEL_SLOT)
  262.                 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
  263.                     mlog("Slot " .. REFUEL_SLOT .. " now contains non-fuel (" .. (current_item_detail.displayName or current_item_detail.name) .. "). Stopping suck.")
  264.                     break
  265.                 end
  266.                 if turtle.suck() then
  267.                     sucked_count = sucked_count + 1
  268.                     sleep(0.1) -- Small delay for items to transfer
  269.                 else
  270.                     mlog("Failed to suck item or chest empty after " .. sucked_count .. " items.")
  271.                     break
  272.                 end
  273.             end
  274.             if sucked_count > 0 then mlog("Sucked " .. sucked_count .. " items to replenish fuel slot.") end
  275.         else
  276.             mlog("Fuel slot full or contains non-fuel item, no replenishment attempted.")
  277.         end
  278.         mlog("Refuel process finished. Current Fuel: " .. turtle.getFuelLevel())
  279.         return true -- refuel always "succeeds" in trying
  280.     end
  281.  
  282.     function ml.setPos(x, y, z, dir_input)
  283.         if type(x) ~= "number" or type(y) ~= "number" or type(z) ~= "number" then
  284.             mlog("Error setting position: x, y, z must be numbers.")
  285.             return false
  286.         end
  287.         current_pos.x = x
  288.         current_pos.y = y
  289.         current_pos.z = z
  290.         current_dir = dirNameToNumber(dir_input)
  291.         mlog("Position manually set to: X:" .. x .. " Y:" .. y .. " Z:" .. z .. " Dir:" .. (DIR_NAMES[current_dir] or "Unknown"))
  292.         ml.savePosition()
  293.         return true
  294.     end
  295.  
  296.     local function turnToDir(target_dir_num)
  297.         if current_dir == nil then mlog("Cannot turn to direction: Current direction unknown."); return false end
  298.         if current_dir == target_dir_num then return true end
  299.  
  300.         mlog("Turning to face " .. (DIR_NAMES[target_dir_num] or "Unknown Target Dir") .. " from " .. (DIR_NAMES[current_dir] or "Unknown Current Dir"))
  301.         local diff = (target_dir_num - current_dir + 4) % 4
  302.         if diff == 1 then return ml.turnRight()
  303.         elseif diff == 2 then return ml.turnAround()
  304.         elseif diff == 3 then return ml.turnLeft()
  305.         end
  306.         return false -- Should not happen if diff is 1,2,3
  307.     end
  308.     ml.turnToDir = turnToDir -- Expose for external use if needed, though moveTo uses it internally
  309.  
  310.     function ml.moveTo(target_x, target_y, target_z, target_dir_input)
  311.         if current_pos.x == nil or current_dir == nil then mlog("Cannot moveTo: Current position or direction unknown."); return false end
  312.  
  313.         local target_dir_num = dirNameToNumber(target_dir_input)
  314.         mlog(string.format("Attempting to move to: X:%s Y:%s Z:%s Dir:%s (Num: %d)",
  315.             tostring(target_x), tostring(target_y), tostring(target_z), (DIR_NAMES[target_dir_num] or "Unknown"), target_dir_num))
  316.  
  317.         local attempts = 0
  318.         local max_attempts = 200 -- Increased attempts for complex moves
  319.         local max_stuck_per_axis = 3
  320.         local stuck_counter = 0
  321.  
  322.         while (current_pos.x ~= target_x or current_pos.y ~= target_y or current_pos.z ~= target_z) and attempts < max_attempts do
  323.             attempts = attempts + 1
  324.             local moved_this_iteration = false
  325.  
  326.             -- Y-axis movement (priority to clear height differences)
  327.             if current_pos.y < target_y then
  328.                 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
  329.             elseif current_pos.y > target_y then
  330.                 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
  331.             end
  332.  
  333.             if moved_this_iteration then goto continue_loop end
  334.  
  335.             -- X-axis movement
  336.             if current_pos.x < target_x then
  337.                 if turnToDir(1) then -- East
  338.                     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
  339.                 end
  340.             elseif current_pos.x > target_x then
  341.                 if turnToDir(3) then -- West
  342.                     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
  343.                 end
  344.             end
  345.  
  346.             if moved_this_iteration then goto continue_loop end
  347.  
  348.             -- Z-axis movement
  349.             if current_pos.z < target_z then -- Target is +Z relative to turtle
  350.                 if turnToDir(0) then -- North
  351.                     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
  352.                 end
  353.             elseif current_pos.z > target_z then -- Target is -Z relative to turtle
  354.                 if turnToDir(2) then -- South
  355.                     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
  356.                 end
  357.             end
  358.  
  359.             ::continue_loop::
  360.             if not moved_this_iteration then
  361.                 mlog("moveTo: No successful move in iteration " .. attempts .. ". Stuck counter: " .. stuck_counter)
  362.                 if stuck_counter >= max_stuck_per_axis then
  363.                     mlog("moveTo: Stuck for " .. stuck_counter .. " iterations on an axis. Aborting moveTo.")
  364.                     break
  365.                 end
  366.             end
  367.             sleep(0.05) -- Small delay between actions
  368.         end
  369.  
  370.         if attempts >= max_attempts then mlog("moveTo: Reached max attempts (" .. max_attempts .. ").") end
  371.  
  372.         -- Final turn to target direction
  373.         if not turnToDir(target_dir_num) then
  374.             mlog("moveTo: Failed to make final turn to target direction.")
  375.         end
  376.  
  377.         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
  378.         if success then mlog("moveTo: Successfully reached target.")
  379.         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",
  380.             tostring(current_pos.x), tostring(current_pos.y), tostring(current_pos.z), (DIR_NAMES[current_dir] or "Unk"),
  381.             tostring(target_x), tostring(target_y), tostring(target_z), (DIR_NAMES[target_dir_num] or "Unk")))
  382.         end
  383.         return success
  384.     end
  385.  
  386.     function ml.init()
  387.         mlog("Initializing MoveLib for MoveBot...")
  388.         automata_p = peripheral.find(AUTOMATA_PERIPHERAL_NAME)
  389.         if not automata_p then
  390.             mlog("WARNING: Automata peripheral ('" .. AUTOMATA_PERIPHERAL_NAME .. "') not found. 'gohome' command will be unavailable.")
  391.         else
  392.             mlog("Automata peripheral ('" .. AUTOMATA_PERIPHERAL_NAME .. "') found.")
  393.         end
  394.  
  395.         if not loadPosition() then
  396.             mlog("Position data not found or corrupt from '" .. POSITION_FILE .. "'.")
  397.             if automata_p then
  398.                 mlog("Attempting to warp home to set initial coordinates (0,0,0). Direction will be preserved (likely unknown if first run).")
  399.                 if ml.home() then -- home() sets current_pos to 0,0,0 if successful. current_dir is NOT changed by warp.
  400.                     mlog("Warped home successfully. Coordinates are 0,0,0. Direction may still be unknown if not previously set.")
  401.                     -- If current_dir was nil, savePosition in home() wouldn't save. This is fine.
  402.                 else
  403.                     mlog("CRITICAL: Failed initial home warp. Position and direction remain unknown.")
  404.                     current_pos = { x = nil, y = nil, z = nil } -- Ensure they are nil
  405.                     current_dir = nil
  406.                 end
  407.             else
  408.                 mlog("CRITICAL: No Automata available and no saved position. Position and direction are unknown.")
  409.                 current_pos = { x = nil, y = nil, z = nil }
  410.                 current_dir = nil
  411.             end
  412.         else
  413.             mlog("Position loaded successfully from '" .. POSITION_FILE .. "'.")
  414.         end
  415.  
  416.         if current_pos.x == nil or current_dir == nil then
  417.             mlog("MoveLib initialized, BUT POSITION OR DIRECTION IS UNKNOWN.")
  418.             mlog("Use '" .. COMMAND_PREFIX .. " setpos <x> <y> <z> <dir>' to set manually.")
  419.             mlog("If at home (0,0,0), you can also try '" .. COMMAND_PREFIX .. " calibratedir'.")
  420.         else
  421.             mlog("MoveLib initialized. Pos: X:" .. current_pos.x .. " Y:" .. current_pos.y .. " Z:" .. current_pos.z .. " Dir:" .. (DIR_NAMES[current_dir] or "Invalid"))
  422.         end
  423.     end
  424.  
  425.     function ml.getPosition() return current_pos end
  426.     function ml.getDirection() return current_dir end
  427.     function ml.getDirectionName()
  428.         if current_dir == nil then return "Unknown" end
  429.         return DIR_NAMES[current_dir] or "Invalid (" .. tostring(current_dir) .. ")"
  430.     end
  431.  
  432.     -- Expose shorthand versions if desired, e.g., ml.l = ml.turnLeft
  433.     ml.l = ml.turnLeft; ml.r = ml.turnRight; ml.f = ml.forward; ml.b = ml.back;
  434.     ml.u = ml.up; ml.d = ml.down; ml.a = ml.turnAround; ml.h = ml.home; ml.m = ml.moveTo;
  435.  
  436. end -- End of Movement Library scope
  437. --#endregion
  438.  
  439. --#region Helper Functions (Chat)
  440. local function sendFormattedChat(messageComponents, recipientUsername)
  441.     logDebug("Attempting to send formatted chat. Recipient: " .. (recipientUsername or "ALL"), "ChatHelper")
  442.     if not chatBox then
  443.         local plainText = ""
  444.         for _, comp in ipairs(messageComponents) do plainText = plainText .. (comp.text or "") end
  445.         local noChatMsg = "[" .. CHAT_BOT_NAME .. "-NoChatBox" .. (recipientUsername and (" to " .. recipientUsername) or "") .. "] " .. plainText
  446.         print(noChatMsg)
  447.         logDebug("ChatBox not found. Printed to console: " .. noChatMsg, "ChatHelper")
  448.         return
  449.     end
  450.  
  451.     local jsonMessage = textutils.serialiseJSON(messageComponents)
  452.     if not jsonMessage then
  453.         local fallbackMsg = "Error: Could not serialize message for formatted sending."
  454.         logDebug("JSON Serialization Error. Fallback: " .. fallbackMsg, "ChatHelper")
  455.         if recipientUsername then
  456.             chatBox.sendMessageToPlayer(fallbackMsg, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  457.         else
  458.             chatBox.sendMessage(fallbackMsg, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  459.         end
  460.         return
  461.     end
  462.  
  463.     local success, err
  464.     if recipientUsername then
  465.         success, err = pcall(chatBox.sendFormattedMessageToPlayer, jsonMessage, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  466.     else
  467.         success, err = pcall(chatBox.sendFormattedMessage, jsonMessage, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  468.     end
  469.  
  470.     if not success then
  471.         logDebug("Error sending formatted message: " .. (err or "Unknown error") .. ". Attempting fallback to plain text.", "ChatHelper")
  472.         local plainText = ""
  473.         for _, comp in ipairs(messageComponents) do plainText = plainText .. (comp.text or "") end
  474.         if recipientUsername then
  475.             chatBox.sendMessageToPlayer(plainText, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  476.         else
  477.             chatBox.sendMessage(plainText, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  478.         end
  479.     else
  480.         logDebug("Formatted message sent successfully.", "ChatHelper")
  481.     end
  482.     os.sleep(0.3) -- Prevent chat spamming issues
  483. end
  484.  
  485. local function announce(messageComponents)
  486.     sendFormattedChat(messageComponents)
  487. end
  488. --#endregion
  489.  
  490. --#region Command Handlers
  491. local commandHandlers = {}
  492.  
  493. commandHandlers.help = function(username, _)
  494.     logDebug("Executing command: help, User: " .. username)
  495.     announce({{text = "--- MoveBot Commands (" .. COMMAND_PREFIX .. ") ---", color = COLORS.GOLD, bold = true}})
  496.     announce({{text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = " - Shows this help message.", color = COLORS.GRAY}})
  497.     announce({{text = COMMAND_PREFIX .. " pos", color = COLORS.AQUA}, {text = " - Shows current position and direction.", color = COLORS.GRAY}})
  498.     announce({{text = COMMAND_PREFIX .. " fuel", color = COLORS.AQUA}, {text = " - Shows current fuel level.", color = COLORS.GRAY}})
  499.     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}})
  500.     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}})
  501.     announce({{text = COMMAND_PREFIX .. " f / b / l / r / u / d / around", color = COLORS.AQUA}, {text = " - Basic movements.", color = COLORS.GRAY}})
  502.     announce({{text = COMMAND_PREFIX .. " gohome", color = COLORS.AQUA}, {text = " - Warps to (0,0,0) via Automata (if available). Direction preserved.", color = COLORS.GRAY}})
  503.     announce({{text = COMMAND_PREFIX .. " refuel", color = COLORS.AQUA}, {text = " - Attempts to refuel from slot " .. REFUEL_SLOT .. " and replenish it.", color = COLORS.GRAY}})
  504.     announce({{text = COMMAND_PREFIX .. " moveto <x> <y> <z> <dir>", color = COLORS.AQUA}, {text = " - Moves to specified coordinates and direction.", color = COLORS.GRAY}})
  505.     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}})
  506. end
  507.  
  508. commandHandlers.pos = function(username, _)
  509.     logDebug("Executing command: pos, User: " .. username)
  510.     local p = ml.getPosition()
  511.     local d = ml.getDirectionName()
  512.     if p.x ~= nil and p.y ~= nil and p.z ~= nil then
  513.         announce({{text = "Current Position: ", color = COLORS.AQUA}, {text = "X:" .. p.x .. " Y:" .. p.y .. " Z:" .. p.z, color = COLORS.WHITE}})
  514.         announce({{text = "Current Direction: ", color = COLORS.AQUA}, {text = d, color = COLORS.WHITE}})
  515.     else
  516.         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}})
  517.     end
  518. end
  519.  
  520. commandHandlers.fuel = function(username, _)
  521.     logDebug("Executing command: fuel, User: " .. username)
  522.     local level = turtle.getFuelLevel()
  523.     local limit = turtle.getFuelLimit()
  524.     announce({{text = "Fuel Level: ", color = COLORS.AQUA}, {text = level .. (limit == 0 and " (Unlimited)" or " / " .. limit), color = COLORS.WHITE}})
  525. end
  526.  
  527. commandHandlers.setpos = function(username, args)
  528.     logDebug("Executing command: setpos, User: " .. username .. ", Args: " .. textutils.serialize(args))
  529.     if #args ~= 4 then
  530.         announce({{text = "Usage: ", color = COLORS.RED}, {text = COMMAND_PREFIX .. " setpos <x> <y> <z> <direction>", color = COLORS.AQUA}})
  531.         announce({{text = "Example: ", color = COLORS.GRAY}, {text = COMMAND_PREFIX .. " setpos 0 0 0 N", color = COLORS.WHITE}})
  532.         return
  533.     end
  534.     local x, y, z = tonumber(args[1]), tonumber(args[2]), tonumber(args[3])
  535.     local dir_input = args[4]
  536.     if not x or not y or not z then
  537.         announce({{text = "Error: X, Y, Z coordinates must be numbers.", color = COLORS.RED}})
  538.         return
  539.     end
  540.     if ml.setPos(x, y, z, dir_input) then
  541.         announce({{text = "Position and direction set successfully.", color = COLORS.GREEN}})
  542.     else
  543.         announce({{text = "Failed to set position (check logs for details).", color = COLORS.RED}})
  544.     end
  545. end
  546.  
  547. commandHandlers.calibratedir = function(username, _)
  548.     logDebug("Executing command: calibratedir, User: " .. username)
  549.     local p = ml.getPosition()
  550.     if not (p.x == 0 and p.y == 0 and p.z == 0) then
  551.         announce({{text = "Error: Must be at home (0,0,0) to use calibratedir this way.", color = COLORS.RED}})
  552.         announce({{text = "Current position: X:"..tostring(p.x).." Y:"..tostring(p.y).." Z:"..tostring(p.z), color = COLORS.YELLOW}})
  553.         return
  554.     end
  555.  
  556.     logDebug("Checking redstone input on 'front' for South calibration.")
  557.     local is_facing_south = false
  558.     local rs_success, rs_value_or_err = pcall(redstone.getInput, "front")
  559.  
  560.     if not rs_success then
  561.         announce({{text = "Error accessing redstone API: ", color = COLORS.RED}, {text=tostring(rs_value_or_err), color=COLORS.YELLOW}})
  562.         logDebug("Redstone API pcall failed: " .. tostring(rs_value_or_err))
  563.         return
  564.     end
  565.     is_facing_south = rs_value_or_err
  566.  
  567.     if is_facing_south then
  568.         local old_dir_name = ml.getDirectionName()
  569.         ml.setPos(p.x, p.y, p.z, 2) -- 2 is South
  570.         announce({{text = "Redstone signal on front detected. Direction calibrated to South.", color = COLORS.GREEN}})
  571.         logDebug("Calibrated direction to South. Was: " .. old_dir_name)
  572.     else
  573.         announce({{text = "No redstone signal on front. Direction not changed.", color = COLORS.YELLOW}})
  574.         announce({{text = "Current direction: ", color = COLORS.AQUA}, {text = ml.getDirectionName(), color = COLORS.WHITE}})
  575.         logDebug("Redstone front input false. Direction remains: " .. ml.getDirectionName())
  576.     end
  577. end
  578.  
  579. local simpleMoveCommands = {
  580.     f = ml.forward, b = ml.back, l = ml.turnLeft, r = ml.turnRight,
  581.     u = ml.up, d = ml.down, around = ml.turnAround
  582. }
  583. for cmd, func in pairs(simpleMoveCommands) do
  584.     commandHandlers[cmd] = function(username, _)
  585.         logDebug("Executing simple move: " .. cmd .. ", User: " .. username)
  586.         if func() then
  587.             announce({{text = "Move '" .. cmd .. "' successful.", color = COLORS.GREEN}})
  588.         else
  589.             announce({{text = "Move '" .. cmd .. "' failed. (Obstacle, unknown state, or no fuel?)", color = COLORS.RED}})
  590.         end
  591.     end
  592. end
  593.  
  594. commandHandlers.gohome = function(username, _)
  595.     logDebug("Executing command: gohome, User: " .. username)
  596.     if ml.home() then
  597.         announce({{text = "Successfully warped home to (0,0,0). Direction preserved.", color = COLORS.GREEN}})
  598.     else
  599.         announce({{text = "Failed to warp home. (Automata not found or warp error).", color = COLORS.RED}})
  600.     end
  601. end
  602.  
  603. commandHandlers.refuel = function(username, _)
  604.     logDebug("Executing command: refuel, User: " .. username)
  605.     ml.refuel() -- refuel function logs verbosely
  606.     announce({{text = "Refuel attempt finished. Current fuel: " .. turtle.getFuelLevel(), color = COLORS.GREEN}})
  607. end
  608.  
  609. commandHandlers.moveto = function(username, args)
  610.     logDebug("Executing command: moveto, User: " .. username .. ", Args: " .. textutils.serialize(args))
  611.     if #args ~= 4 then
  612.         announce({{text = "Usage: ", color = COLORS.RED}, {text = COMMAND_PREFIX .. " moveto <x> <y> <z> <direction>", color = COLORS.AQUA}})
  613.         return
  614.     end
  615.     local x, y, z = tonumber(args[1]), tonumber(args[2]), tonumber(args[3])
  616.     local dir_input = args[4]
  617.     if not x or not y or not z then
  618.         announce({{text = "Error: X, Y, Z coordinates must be numbers.", color = COLORS.RED}})
  619.         return
  620.     end
  621.  
  622.     announce({{text = "Attempting to move to X:"..x.." Y:"..y.." Z:"..z.." Dir:"..dir_input.."...", color = COLORS.YELLOW}})
  623.     if ml.moveTo(x, y, z, dir_input) then
  624.         announce({{text = "Successfully moved to target.", color = COLORS.GREEN}})
  625.     else
  626.         announce({{text = "Failed to reach target or make final turn.", color = COLORS.RED}})
  627.     end
  628. end
  629.  
  630. commandHandlers.mult = function(username, args)
  631.     logDebug("Executing command: mult, User: " .. username .. ", Args: " .. textutils.serialize(args))
  632.     if #args ~= 1 or type(args[1]) ~= "string" then
  633.         announce({{text = "Usage: ", color = COLORS.YELLOW}, {text = COMMAND_PREFIX .. " mult <sequence>", color = COLORS.AQUA}})
  634.         announce({{text = "Sequence uses single letters: l,r,f,b,u,d,a. Example: lffr", color = COLORS.GRAY}})
  635.         return
  636.     end
  637.     local sequence = string.lower(args[1])
  638.     announce({{text = "Executing sequence: ", color = COLORS.AQUA}, {text = sequence, color = COLORS.WHITE}})
  639.     local success = true
  640.     for i = 1, #sequence do
  641.         local move_char = string.sub(sequence, i, i)
  642.         local move_func = simpleMoveCommands[move_char] or (move_char == 'a' and ml.turnAround)
  643.  
  644.         if move_func then
  645.             announce({{text = "Executing '", color = COLORS.GRAY}, {text = move_char, color = COLORS.YELLOW}, {text = "'...", color = COLORS.GRAY}})
  646.             if not move_func() then
  647.                 announce({{text = "Move '", color = COLORS.RED}, {text = move_char, color = COLORS.YELLOW}, {text = "' failed. Stopping sequence.", color = COLORS.RED}})
  648.                 if turtle.getFuelLevel() < 10 and turtle.getFuelLimit() ~= 0 then
  649.                      announce({{text="CRITICAL: Fuel low ("..turtle.getFuelLevel()..")!",c=COLORS.DARK_RED,b=true}});
  650.                 end
  651.                 success = false
  652.                 break
  653.             end
  654.             sleep(0.2) -- Small delay between moves in sequence
  655.         else
  656.             announce({{text = "Unknown character '", color = COLORS.RED}, {text = move_char, color = COLORS.YELLOW}, {text = "' in sequence. Stopping.", color = COLORS.RED}})
  657.             success = false
  658.             break
  659.         end
  660.     end
  661.     if success then
  662.         announce({{text = "Sequence finished.", color = COLORS.GREEN}})
  663.     end
  664. end
  665.  
  666. --#endregion
  667.  
  668. --#region Main Loop
  669. local function run()
  670.     term.clear(); term.setCursorPos(1, 1)
  671.  
  672.     if DEBUG_MODE then
  673.         local file, err = fs.open(DEBUG_LOG_FILE, "w") -- Clear log on start
  674.         if file then
  675.             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))
  676.             file.write("======================================================================\n")
  677.             file.close()
  678.         else
  679.             print("DEBUG LOG ERROR: Could not clear/initialize " .. DEBUG_LOG_FILE .. ": " .. (err or "unknown error"))
  680.         end
  681.     end
  682.     logDebug("Script run() started.")
  683.  
  684.     if not chatBox then
  685.         logDebug("WARNING: Chat Box peripheral ('" .. CHAT_BOX_PERIPHERAL_NAME .. "') not found! Chat features will be printed to console only.")
  686.         print("WARNING: Chat Box ('" .. CHAT_BOX_PERIPHERAL_NAME .. "') not found! Chat features disabled/printed to console.")
  687.     end
  688.  
  689.     -- Initialize Movement Library
  690.     ml.init() -- This will log its own status, including if position is unknown
  691.  
  692.     logDebug("Announcing online status.")
  693.     print(CHAT_BOT_NAME .. " script started. Type '" .. COMMAND_PREFIX .. " help' in chat or '@all'.")
  694.     if chatBox then
  695.         announce({{text = CHAT_BOT_NAME .. " online!", color = COLORS.GREEN, bold = true}, {text = " Ready for movement commands.", color = COLORS.GRAY}})
  696.         announce({{text = "Type '", color = COLORS.GRAY}, {text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = "' or '@all' for commands.", color = COLORS.GRAY}})
  697.         if ml.getPosition().x == nil or ml.getDirection() == nil then
  698.             announce({{text = "WARNING: My position or direction is unknown. Please use '", color = COLORS.YELLOW, bold=true},
  699.                       {text = COMMAND_PREFIX.." setpos", color=COLORS.AQUA, bold=true},
  700.                       {text = "' or '", color = COLORS.YELLOW, bold=true},
  701.                       {text = COMMAND_PREFIX.." calibratedir", color=COLORS.AQUA, bold=true},
  702.                       {text = "' to initialize me.", color = COLORS.YELLOW, bold=true}})
  703.         end
  704.     end
  705.  
  706.     while true do
  707.         local eventData = {os.pullEvent()}
  708.         local eventType = eventData[1]
  709.         logDebug("Event received: " .. eventType .. " - Data: " .. textutils.serialize(eventData, {compact = true, max_depth = 2}), "EventLoop")
  710.  
  711.         if eventType == "chat" then
  712.             local eUsername, eMessage, _, eIsHidden = eventData[2], eventData[3], eventData[4], eventData[5]
  713.             if not eIsHidden and eMessage then
  714.                 if string.lower(eMessage) == "@all" then
  715.                     logDebug("@all command received from " .. eUsername)
  716.                     -- Green and Magenta for @all response
  717.                     announce({{text = "Hi @all! For " .. CHAT_BOT_NAME .. " commands, type: ", color = COLORS.GREEN}, {text = COMMAND_PREFIX .. " help", color = COLORS.LIGHT_PURPLE}})
  718.                 elseif string.sub(eMessage, 1, #COMMAND_PREFIX) == COMMAND_PREFIX then
  719.                     logDebug("Chat command received from " .. eUsername .. ": " .. eMessage)
  720.                     local parts = {}
  721.                     for part in string.gmatch(eMessage, "[^%s]+") do
  722.                         table.insert(parts, part)
  723.                     end
  724.  
  725.                     local commandName = ""
  726.                     if parts[2] then commandName = string.lower(parts[2]) end
  727.                     local cmdArgs = {}
  728.                     for i = 3, #parts do table.insert(cmdArgs, parts[i]) end
  729.  
  730.                     logDebug("Parsed command: '" .. commandName .. "', Args: " .. textutils.serialize(cmdArgs))
  731.  
  732.                     if commandHandlers[commandName] then
  733.                         local success_pcall, err_pcall = pcall(commandHandlers[commandName], eUsername, cmdArgs)
  734.                         if not success_pcall then
  735.                             logDebug("Error executing command '" .. commandName .. "': " .. tostring(err_pcall))
  736.                             announce({{text = "Oops! Something went wrong while processing your command: ", color = COLORS.RED}, {text = commandName, color = COLORS.YELLOW}})
  737.                             announce({{text = "Error details: ", color = COLORS.RED}, {text = tostring(err_pcall), color = COLORS.YELLOW}})
  738.                         end
  739.                     elseif commandName ~= "" then
  740.                         announce({{text = "Unknown command: '", color = COLORS.RED}, {text = commandName, color = COLORS.YELLOW},
  741.                                   {text = "'. Try '", color = COLORS.RED}, {text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = "'.", color = COLORS.RED}})
  742.                     end
  743.                 end
  744.             end
  745.         elseif eventType == "terminate" then
  746.             logDebug("Terminate event received. Shutting down.")
  747.             if chatBox then
  748.                 announce({{text = CHAT_BOT_NAME .. " shutting down...", color = COLORS.YELLOW, bold = true}})
  749.             end
  750.             logDebug("Script terminated.")
  751.             print(CHAT_BOT_NAME .. " terminated.")
  752.             return
  753.         end
  754.     end
  755. end
  756.  
  757. run()
  758. --#endregion
Add Comment
Please, Sign In to add comment