Myros27

DefragBot V4 Silent

May 18th, 2025 (edited)
31
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 46.12 KB | None | 0 0
  1. --[[
  2.     DefragBot v4 - Final Polish & Cmd Help
  3.     - IO Port "Waiting..." message is now log-only; timeout is announced.
  4.     - Cycle ends with warp home and full refuel before completion announcement.
  5.     - Added '@defrag cmd help' for specific cmd usage.
  6.     - Improved "Unknown Command" message to suggest 'cmd' if input looks like a function call.
  7. ]]
  8.  
  9. --#region Configuration
  10. local CHAT_BOX_PERIPHERAL_NAME = "chatBox"
  11. local COMMAND_PREFIX = "@defrag"
  12. local CHAT_BOT_NAME = "DefragBot"
  13. local CHAT_BOT_BRACKETS = "[]"
  14. local CHAT_BOT_BRACKET_COLOR = "&3" -- Cyan
  15.  
  16. local DEBUG_LOGGING_ENABLED = false
  17. local LOG_FILE_NAME = "defrag.log"
  18. --#endregion
  19.  
  20. --#region Global State
  21. -- local lastAnnounceTime = 0 -- Removed for simpler announce
  22. local chatBox = nil
  23. local chatBoxFunctional = true -- Assume functional until a send error specifically
  24. --#endregion
  25.  
  26. --#region Logger Setup
  27. local function writeToLogFile(message) if not DEBUG_LOGGING_ENABLED then return end; local f,e=fs.open(LOG_FILE_NAME,"a"); if f then f.write(string.format("[%s] %s\n",os.date("%Y-%m-%d %H:%M:%S"),message));f.close() else print("LOG ERR: "..tostring(e))end end
  28. local function botLog(msg)local fm="["..CHAT_BOT_NAME.."-Debug] "..msg; print(fm); writeToLogFile(fm)end
  29. local function moveLog(msg)local fm="[MoveLib-Debug] "..msg; print(fm); writeToLogFile(fm)end
  30. --#endregion
  31.  
  32. --#region Movement Library Code (Integrated - Assumed same as v1.7)
  33. local AUTOMATA_PERIPHERAL_NAME="endAutomata";local POSITION_FILE="turtle_pos.json";local REFUEL_SLOT=16;local FUEL_ITEM_NAME_PART="coal";local automata=nil;local current_pos={x=nil,y=nil,z=nil};local current_dir=nil;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 savePosition()if current_pos.x==nil or current_dir==nil then moveLog("No save, state unknown.");return end;local d={x=current_pos.x,y=current_pos.y,z=current_pos.z,dir=current_dir};local f,e=fs.open(POSITION_FILE,"w");if f then f.write(textutils.serialiseJSON(d));f.close();moveLog("Pos saved: X:"..d.x.." Y:"..d.y.." Z:"..d.z.." Dir:"..(DIR_NAMES[d.dir]or"Unk"))else moveLog("Err save pos: "..(e or"unk"))end end;local function loadPosition()if fs.exists(POSITION_FILE)then local f,e=fs.open(POSITION_FILE,"r");if f then local sD=f.readAll();f.close();local s,d=pcall(textutils.unserialiseJSON,sD);if s and d and d.x~=nil and d.y~=nil and d.z~=nil and d.dir~=nil then current_pos.x=d.x;current_pos.y=d.y;current_pos.z=d.z;current_dir=d.dir;moveLog("Pos loaded: X:"..current_pos.x.." Y:"..current_pos.y.." Z:"..current_pos.z.." Dir:"..(DIR_NAMES[current_dir]or"Unk"));return true else moveLog("Fail parse pos file: "..tostring(d))end else moveLog("Err open pos file: "..(e or"unk"))end else moveLog("Pos file not found.")end;return false end;local function dirNameToNumber(n)n=string.lower(n or"");if n=="n"or n=="north"then return 0 elseif n=="e"or n=="east"then return 1 elseif n=="s"or n=="south"then return 2 elseif n=="w"or n=="west"then return 3 end;moveLog("Warn: Invalid dir '"..(n or"nil").."'. Default N.");return 0 end;local function turnLeft()if current_dir==nil then moveLog("No turn: dir unk.");return false end;if turtle.turnLeft()then current_dir=(current_dir-1+4)%4;moveLog("Left. Dir:"..(DIR_NAMES[current_dir]or"Unk"));savePosition();return true else moveLog("Fail L turn.");return false end end;local function turnRight()if current_dir==nil then moveLog("No turn: dir unk.");return false end;if turtle.turnRight()then current_dir=(current_dir+1)%4;moveLog("Right. Dir:"..(DIR_NAMES[current_dir]or"Unk"));savePosition();return true else moveLog("Fail R turn.");return false end end;local function turnAround()if current_dir==nil then moveLog("No turn around: dir unk.");return false end;moveLog("Turn around...");if turnRight()and turnRight()then moveLog("Around. Dir:"..(DIR_NAMES[current_dir]or"Unk"));return true else moveLog("Fail turn around.");return false end end;local function forward()if current_pos.x==nil then moveLog("No move: pos unk.");return false end;if turtle.getFuelLevel()<1 and turtle.getFuelLimit()~=0 then moveLog("Fwd: <1 fuel.");return false end;if turtle.forward()then local v=DIR_VECTORS[current_dir];current_pos.x=current_pos.x+v.x;current_pos.y=current_pos.y+v.y;current_pos.z=current_pos.z+v.z;moveLog("Fwd. Pos: X:"..current_pos.x.." Y:"..current_pos.y.." Z:"..current_pos.z);savePosition();return true else moveLog("Fail fwd (block).");return false end end;local function back()if current_pos.x==nil then moveLog("No move: pos unk.");return false end;if turtle.getFuelLevel()<1 and turtle.getFuelLimit()~=0 then moveLog("Back: <1 fuel.");return false end;if turtle.back()then local v=DIR_VECTORS[current_dir];current_pos.x=current_pos.x-v.x;current_pos.y=current_pos.y-v.y;current_pos.z=current_pos.z-v.z;moveLog("Back. Pos: X:"..current_pos.x.." Y:"..current_pos.y.." Z:"..current_pos.z);savePosition();return true else moveLog("Fail back (block).");return false end end;local function up()if current_pos.y==nil then moveLog("No up: Y pos unk.");return false end;if turtle.getFuelLevel()<1 and turtle.getFuelLimit()~=0 then moveLog("Up: <1 fuel.");return false end;if turtle.up()then current_pos.y=current_pos.y+1;moveLog("Up. Pos: X:"..current_pos.x.." Y:"..current_pos.y.." Z:"..current_pos.z);savePosition();return true else moveLog("Fail up (block/no fuel).");return false end end;local function down()if current_pos.y==nil then moveLog("No down: Y pos unk.");return false end;if turtle.down()then current_pos.y=current_pos.y-1;moveLog("Down. Pos: X:"..current_pos.x.." Y:"..current_pos.y.." Z:"..current_pos.z);savePosition();return true else moveLog("Fail down (block).");return false end end;local function home()if not automata then automata=peripheral.find(AUTOMATA_PERIPHERAL_NAME);if not automata then moveLog("Err: EndAuto ('"..AUTOMATA_PERIPHERAL_NAME.."') miss.");return false end end;moveLog("Warp home...");local s,r=pcall(function()return automata.warpToPoint("home")end);if s and r then moveLog("Warp home OK.");current_pos.x=0;current_pos.y=0;current_pos.z=0;current_dir=0;moveLog("Pos reset home (000N).");savePosition();return true else moveLog("Fail warp home: "..tostring(r or"pcall err"));return false end end;local function refuel()moveLog("Refuel...");turtle.select(REFUEL_SLOT);local iBFS=turtle.getItemCount(REFUEL_SLOT);local iD=turtle.getItemDetail(REFUEL_SLOT);local iN="";if iD and iD.name then iN=string.lower(iD.name)else moveLog("Fuel slot "..REFUEL_SLOT.." empty/no name.")end;if iBFS>0 and string.find(iN,FUEL_ITEM_NAME_PART)then moveLog("Refuel from slot "..REFUEL_SLOT.." ("..(iD.name or"Unk Fuel").." x"..iBFS..")");if turtle.refuel(0)then local iAFS=turtle.getItemCount(REFUEL_SLOT);local cI=iBFS-iAFS;if cI>0 then moveLog("Refueled OK. Consumed "..cI.." "..(iD.name or"fuel")..".")else moveLog("Refueled, 0 consumed. Fuel: "..turtle.getFuelLevel())end else moveLog("turtle.refuel(0) fail. Fuel: "..turtle.getFuelLevel())end elseif iBFS>0 then moveLog("Item in fuel slot not fuel: "..(iD and iD.name or"Unk"))else moveLog("Fuel slot "..REFUEL_SLOT.." init empty.")end;local iTS=64-turtle.getItemCount(REFUEL_SLOT);local sTC=0;if iTS>0 then moveLog("Suck "..iTS.." to replenish fuel slot...");for _=1,iTS do if turtle.getItemCount(REFUEL_SLOT)>=64 then break end;local cSD=turtle.getItemDetail(REFUEL_SLOT);local cSN="";if cSD and cSD.name then cSN=string.lower(cSD.name)end;if turtle.getItemCount(REFUEL_SLOT)>0 and not string.find(cSN,FUEL_ITEM_NAME_PART)then moveLog("Slot "..REFUEL_SLOT.." has non-fuel ("..(cSD.name or"Unk").."). Stop suck.");break end;if turtle.suck()then sTC=sTC+1 else moveLog("Fail suck/chest empty after "..sTC.." items.");break end;sleep(0.1)end;if sTC>0 then moveLog("Sucked "..sTC.." items to replenish.")end else moveLog("Fuel slot full/non-fuel, no suck.")end;moveLog("Refuel finish. Fuel: "..turtle.getFuelLevel());return true end;local function setPos(x,y,z,dir)if type(x)~="number"or type(y)~="number"or type(z)~="number"then moveLog("Err: setPos xyz num.");return false end;current_pos.x=x;current_pos.y=y;current_pos.z=z;if type(dir)=="number"then current_dir=dir%4 else current_dir=dirNameToNumber(dir)end;moveLog("Pos set: X:"..x.." Y:"..y.." Z:"..z.." Dir:"..(DIR_NAMES[current_dir]or"Unk"));savePosition();return true end;local function turnToDir(td)if current_dir==nil then moveLog("No turnToDir: dir unk.");return false end;if current_dir==td then return true end;moveLog("Turn to dir: "..(DIR_NAMES[td]or"Unk"));local df=(td-current_dir+4)%4;if df==1 then return turnRight()elseif df==2 then return turnAround()elseif df==3 then return turnLeft()end;return false end;local function moveTo(tx,ty,tz,tdir)if current_pos.x==nil then moveLog("No moveTo: pos unk.");return false end;if turtle.getFuelLevel()<10 and turtle.getFuelLimit()~=0 then if ty>current_pos.y then moveLog("moveTo: Low fuel ("..turtle.getFuelLevel()..") need up.")end end;moveLog(string.format("MoveTo: Tgt X%s Y%s Z%s Dir%s",tostring(tx),tostring(ty),tostring(tz),tostring(tdir)));local tdn;if type(tdir)=="number"then tdn=tdir%4 else tdn=dirNameToNumber(tdir)end;local att=0;local max_att=100;local max_stuck_ax=3;while(current_pos.x~=tx or current_pos.y~=ty or current_pos.z~=tz)and att<max_att do att=att+1;local moved=false;local cur_ax_stuck=0;if current_pos.y<ty then if up()then moved=true else cur_ax_stuck=cur_ax_stuck+1;moveLog("moveTo: Block UP("..cur_ax_stuck..")");if(current_pos.x~=tx or current_pos.z~=tz)and cur_ax_stuck<max_stuck_ax then elseif cur_ax_stuck<max_stuck_ax then if turnToDir(math.random(0,3))and forward()then moved=true end else moveLog("moveTo: Stuck Y(up). Abort Y.");ty=current_pos.y end end elseif current_pos.y>ty then if down()then moved=true else cur_ax_stuck=cur_ax_stuck+1;moveLog("moveTo: Block DOWN("..cur_ax_stuck..")");if(current_pos.x~=tx or current_pos.z~=tz)and cur_ax_stuck<max_stuck_ax then elseif cur_ax_stuck<max_stuck_ax then if turnToDir(math.random(0,3))and forward()then moved=true end else moveLog("moveTo: Stuck Y(down). Abort Y.");ty=current_pos.y end end end;if moved then goto continue_main_loop end;cur_ax_stuck=0;if current_pos.x<tx then if turnToDir(1)and forward()then moved=true else cur_ax_stuck=cur_ax_stuck+1;moveLog("moveTo: Block EAST("..cur_ax_stuck..")");if current_pos.z~=tz and cur_ax_stuck<max_stuck_ax then elseif cur_ax_stuck<max_stuck_ax then if turnToDir((math.random(0,1)*2))and forward()then moved=true end else moveLog("moveTo: Stuck X(east). Abort X.");tx=current_pos.x end end elseif current_pos.x>tx then if turnToDir(3)and forward()then moved=true else cur_ax_stuck=cur_ax_stuck+1;moveLog("moveTo: Block WEST("..cur_ax_stuck..")");if current_pos.z~=tz and cur_ax_stuck<max_stuck_ax then elseif cur_ax_stuck<max_stuck_ax then if turnToDir((math.random(0,1)*2))and forward()then moved=true end else moveLog("moveTo: Stuck X(west). Abort X.");tx=current_pos.x end end end;if moved then goto continue_main_loop end;cur_ax_stuck=0;if current_pos.z<tz then if turnToDir(0)and forward()then moved=true else cur_ax_stuck=cur_ax_stuck+1;moveLog("moveTo: Block NORTH("..cur_ax_stuck..")");if current_pos.x~=tx and cur_ax_stuck<max_stuck_ax then elseif cur_ax_stuck<max_stuck_ax then if turnToDir((math.random(0,1)*2)+1)and forward()then moved=true end else moveLog("moveTo: Stuck Z(north). Abort Z.");tz=current_pos.z end end elseif current_pos.z>tz then if turnToDir(2)and forward()then moved=true else cur_ax_stuck=cur_ax_stuck+1;moveLog("moveTo: Block SOUTH("..cur_ax_stuck..")");if current_pos.x~=tx and cur_ax_stuck<max_stuck_ax then elseif cur_ax_stuck<max_stuck_ax then if turnToDir((math.random(0,1)*2)+1)and forward()then moved=true end else moveLog("moveTo: Stuck Z(south). Abort Z.");tz=current_pos.z end end end;if moved then goto continue_main_loop end;if not moved then moveLog("moveTo: No move. Att: "..att);if cur_ax_stuck>=max_stuck_ax then break end end;::continue_main_loop::;sleep(0.05)end;if att>=max_att then moveLog("moveTo: Max att.")end;if not turnToDir(tdn)then moveLog("moveTo: Fail final turn.")end;local s=current_pos.x==tx and current_pos.y==ty and current_pos.z==tz and current_dir==tdn;if s then moveLog("moveTo: OK.")else moveLog(string.format("moveTo: Finish. Pos 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")))end;return s end;local function initMoveLibrary()moveLog("Init MoveLib...");automata=peripheral.find(AUTOMATA_PERIPHERAL_NAME);if not automata then moveLog("CRIT WARN: EndAuto ('"..AUTOMATA_PERIPHERAL_NAME.."') miss. Home warp fail.")end;if not loadPosition()then moveLog("Pos unk/load fail. Warp home & set default.");if home()then moveLog("Init home OK.")else moveLog("CRIT: Fail init home. Pos unk.");current_pos.x=nil;current_pos.y=nil;current_pos.z=nil;current_dir=nil end end;moveLog("MoveLib Init. Pos: X:"..tostring(current_pos.x).." Y:"..tostring(current_pos.y).." Z:"..tostring(current_pos.z).." Dir:"..(current_dir and DIR_NAMES[current_dir]or"Unk"))end;local function getPosition()return current_pos end;local function getDirection()return current_dir end;local function getDirectionName()return current_dir and DIR_NAMES[current_dir]or"Unknown"end;local ml={l=turnLeft,turnLeft=turnLeft,r=turnRight,turnRight=turnRight,f=forward,forward=forward,b=back,back=back,a=turnAround,turnAround=turnAround,u=up,up=up,d=down,down=down,h=home,home=home,refuel=refuel,setPos=setPos,m=moveTo,moveTo=moveTo,getPosition=getPosition,getDirection=getDirection,getDirectionName=getDirectionName,init=initMoveLibrary};initMoveLibrary();
  34. --#endregion End of Integrated Movement Library
  35.  
  36. --#region DefragBot Specific Data & Logic
  37. local LOCATIONS={home={x=0,y=0,z=0,dir='N',name="Refuel Station"},io_port={x=1,y=0,z=0,dir='N',name="IO Port (Disk Input)"},hopper={x=1,y=1,z=0,dir='N',name="Hopper"},chest_temp_storage={x=1,y=2,z=0,dir='N',name="Chest (Temp Disk Storage)"}}
  38. local ME_DRIVE_BAY_RANGES={{y=0,x_start=-9,x_end=-2,dir='N',name="Lower Bay Row 1"},{y=2,x_start=-9,x_end=-2,dir='N',name="Lower Bay Row 2"},{y=4,x_start=-9,x_end=-2,dir='N',name="Lower Bay Row 3"},{y=9,x_start=-9,x_end=-2,dir='N',name="Upper Bay Row 1"},{y=11,x_start=-9,x_end=-2,dir='N',name="Upper Bay Row 2"},{y=13,x_start=-9,x_end=-2,dir='N',name="Upper Bay Row 3"},}
  39. local function getAllMeDriveBayCoordinates()local cs={};for _,br in ipairs(ME_DRIVE_BAY_RANGES)do for x=br.x_start,br.x_end do table.insert(cs,{x=x,y=br.y,z=0,dir=br.dir,name=br.name.." (X:"..x..")"})end end;return cs end
  40. local ALL_ME_DRIVE_LOCATIONS=getAllMeDriveBayCoordinates()
  41. local isDefragRunning=false;local fuelWarningSent=false
  42. --#endregion
  43.  
  44. --#region DefragBot Peripherals and Helpers
  45. local function sendFormattedChat(messageComponents) -- Simplified, always global
  46.     local plainText = ""
  47.     if type(messageComponents) == "table" then
  48.         for _,c_comp in ipairs(messageComponents) do
  49.             plainText = plainText .. (type(c_comp) == "table" and c_comp.text or tostring(c_comp) or "")
  50.         end
  51.     elseif messageComponents ~= nil then plainText = tostring(messageComponents)
  52.     else plainText = "nil_message_components_to_sendFormattedChat" end
  53.    
  54.     if not chatBox then -- Check if chatBox was found at startup
  55.         botLog("[NoChat] " .. plainText);
  56.         return false
  57.     end
  58.    
  59.     local jsonMessage_success, jsonMessage = pcall(textutils.serialiseJSON, messageComponents)
  60.     if not jsonMessage_success then
  61.         botLog("!!! textutils.serialiseJSON FAILED: " .. tostring(jsonMessage) .. " -- Original: " .. textutils.serialize(messageComponents,{compact=true,max_depth=2}))
  62.         print("ERROR: Could not serialize chat message!"); return false
  63.     end
  64.  
  65.     local peripheral_call_success, peripheral_error = pcall(function()
  66.         return chatBox.sendFormattedMessage(jsonMessage, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  67.     end)
  68.    
  69.     if not peripheral_call_success then
  70.         botLog("!!! chatBox.sendFormattedMessage FAILED: " .. tostring(peripheral_error) .. " -- JSON was: " .. jsonMessage)
  71.         print("ERROR: Failed to send formatted message via chatBox: " .. tostring(peripheral_error))
  72.         return false
  73.     end
  74.     return true
  75. end
  76.  
  77. local function announce(messageComponents)
  78.     local success = sendFormattedChat(messageComponents)
  79.     if not success then
  80.         botLog("Announce: sendFormattedChat failed.")
  81.     end
  82.     sleep(0.3)
  83.     return success
  84. end
  85.  
  86. local COLORS={GOLD="gold",AQUA="aqua",GRAY="gray",RED="red",GREEN="green",YELLOW="yellow",WHITE="white",DARK_RED="dark_red",DARK_GRAY="dark_gray"}
  87. --#endregion
  88.  
  89. --#region DefragBot Command Handlers
  90. local commandHandlers = {}
  91.  
  92. commandHandlers.help = function(u,_)
  93.     announce({{text="--- DefragBot Cmds ("..COMMAND_PREFIX..") ---",c=COLORS.GOLD,b=true}})
  94.     announce({{text=COMMAND_PREFIX.." help",c=COLORS.AQUA},{text=" - Shows this help message.",c=COLORS.GRAY}})
  95.     -- VVVVVV MODIFIED CMD HELP ENTRY VVVVVV
  96.     announce({{text=COMMAND_PREFIX.." cmd help",c=COLORS.AQUA},{text=" - Shows help for raw Lua command execution.",c=COLORS.GRAY}})
  97.     announce({{text=COMMAND_PREFIX.." cmd <lua_code>",c=COLORS.AQUA},{text=" - Executes Lua code (e.g., ml.forward()).",c=COLORS.GRAY}})
  98.     -- ^^^^^^ END OF MODIFIED CMD HELP ENTRY ^^^^^^
  99.     announce({{text=COMMAND_PREFIX.." mult <seq>",c=COLORS.AQUA},{text=" - Executes a sequence of simple moves (l,r,f,b,u,d,a).",c=COLORS.GRAY}})
  100.     announce({{text=COMMAND_PREFIX.." pos",c=COLORS.AQUA},{text=" - Shows current position and direction.",c=COLORS.GRAY}})
  101.     announce({{text=COMMAND_PREFIX.." fuel",c=COLORS.AQUA},{text=" - Shows current fuel level.",c=COLORS.GRAY}})
  102.     announce({{text=COMMAND_PREFIX.." unstuck",c=COLORS.AQUA},{text=" - Warps home and resets position.",c=COLORS.GRAY}})
  103.     announce({{text=COMMAND_PREFIX.." startdefrag",c=COLORS.AQUA},{text=" - Starts the ME Drive defragmentation cycle.",c=COLORS.GRAY}})
  104.     announce({{text=COMMAND_PREFIX.." stopdefrag",c=COLORS.AQUA},{text=" - Stops the current defragmentation cycle.",c=COLORS.GRAY}})
  105. end
  106.  
  107. -- VVVVVV MODIFIED CMD HANDLER VVVVVV
  108. commandHandlers.cmd = function(u,a)
  109.     if #a == 0 then
  110.         announce({{text="Usage: "..COMMAND_PREFIX.." cmd <lua_code_to_run>",c=COLORS.YELLOW}})
  111.         announce({{text="Try '",c=COLORS.GRAY},{text=COMMAND_PREFIX.." cmd help",c=COLORS.AQUA},{text="' for more info.",c=COLORS.GRAY}})
  112.         return
  113.     end
  114.  
  115.     if #a == 1 and string.lower(a[1]) == "help" then
  116.         announce({{text="--- @defrag cmd Help ---",c=COLORS.GOLD,b=true}})
  117.         announce({{text="Usage: ",c=COLORS.AQUA},{text=COMMAND_PREFIX.." cmd <lua_code_to_run>",c=COLORS.WHITE}})
  118.         announce({{text="Executes the provided Lua code. You can use 'ml.' to access Movement Library functions.",c=COLORS.GRAY}})
  119.         announce({{text="Examples:",c=COLORS.AQUA}})
  120.         announce({{text="  "..COMMAND_PREFIX.." cmd ml.moveTo(0,0,0,'N')",c=COLORS.WHITE}})
  121.         announce({{text="  "..COMMAND_PREFIX.." cmd ml.turnRight()",c=COLORS.WHITE}})
  122.         announce({{text="  "..COMMAND_PREFIX.." cmd return ml.getFuelLevel()",c=COLORS.WHITE}})
  123.         announce({{text="  "..COMMAND_PREFIX.." cmd turtle.dig()",c=COLORS.WHITE}})
  124.         announce({{text="Note: The Lua code itself is case-sensitive (e.g., 'ml.moveTo', not 'ml.moveto').",c=COLORS.YELLOW}})
  125.         announce({{text="The command must return a value to see a result in chat, otherwise it's 'nil'.",c=COLORS.GRAY}})
  126.         return
  127.     end
  128.  
  129.     local cs=table.concat(a," ")
  130.     botLog("User "..u.." executing raw command: "..cs)
  131.     -- The 'ml' table is exposed to the loaded string.
  132.     local fn,e=load("return "..cs,"raw_user_command","t",{ml=ml, turtle=turtle, peripheral=peripheral, os=os, fs=fs, textutils=textutils, http=http, parallel=parallel, term=term, sleep=sleep, print=print}) -- Expose common globals
  133.    
  134.     if not fn then
  135.         announce({{text="Error parsing command: ",c=COLORS.RED},{text=tostring(e),c=COLORS.YELLOW}})
  136.         botLog("Parse error for raw command '"..cs.."': "..tostring(e))
  137.         return
  138.     end
  139.    
  140.     local s,r=pcall(fn)
  141.     if s then
  142.         announce({{text="Cmd executed. Result: ",c=COLORS.GREEN},{text=tostring(r),c=COLORS.WHITE}})
  143.         botLog("Raw command '"..cs.."' result: "..tostring(r))
  144.     else
  145.         announce({{text="Error executing command: ",c=COLORS.RED},{text=tostring(r),c=COLORS.YELLOW}})
  146.         botLog("Execution error for raw command '"..cs.."': "..tostring(r))
  147.     end
  148. end
  149. -- ^^^^^^ END OF MODIFIED CMD HANDLER ^^^^^^
  150.  
  151. commandHandlers.mult = function(u,a)
  152.     if #a~=1 or type(a[1])~="string"then announce({{text="Usage: "..COMMAND_PREFIX.." mult <seq>",c=COLORS.YELLOW}});announce({{text="Seq: lrfbuda. Ex: lffr",c=COLORS.GRAY}});return end
  153.     local sq=string.lower(a[1]);botLog("Usr "..u.." mult: "..sq);announce({{text="Executing sequence: ",c=COLORS.AQUA},{text=sq,c=COLORS.WHITE}});local sc=true
  154.     for i=1,#sq do
  155.         local mc=string.sub(sq,i,i);local mf=nil
  156.         if mc=="l"then mf=ml.l elseif mc=="r"then mf=ml.r elseif mc=="f"then mf=ml.f elseif mc=="b"then mf=ml.b
  157.         elseif mc=="u"then mf=ml.u elseif mc=="d"then mf=ml.d elseif mc=="a"then mf=ml.a end
  158.         if mf then
  159.             announce({{text="Exec: ",c=COLORS.GRAY},{text=mc,c=COLORS.YELLOW}})
  160.             local ms=mf()
  161.             if not ms then
  162.                 announce({{text="Move '",c=COLORS.RED},{text=mc,c=COLORS.YELLOW},{text="' failed. Stopping sequence.",c=COLORS.RED}})
  163.                 if turtle.getFuelLevel()<10 and turtle.getFuelLimit()~=0 and not fuelWarningSent then
  164.                     announce({{text="CRITICAL: Fuel low ("..turtle.getFuelLevel()..")! Consider '@defrag unstuck'.",c=COLORS.DARK_RED,b=true}});fuelWarningSent=true
  165.                 end
  166.                 sc=false;break
  167.             end
  168.         else announce({{text="Unknown char '",c=COLORS.RED},{text=mc,c=COLORS.YELLOW},{text="' in sequence. Stopping.",c=COLORS.RED}});sc=false;break end
  169.     end
  170.     if sc then announce({{text="Sequence finished.",c=COLORS.GREEN}})end
  171. end
  172. commandHandlers.pos = function(u,_)
  173.     local pD=ml.getPosition();local dNS=ml.getDirectionName()
  174.     if pD and pD.x~=nil then announce({{text="Position: ",c=COLORS.AQUA},{text="X:"..tostring(pD.x).." Y:"..tostring(pD.y).." Z:"..tostring(pD.z),c=COLORS.WHITE},{text=" Facing: "..dNS,c=COLORS.WHITE}})
  175.     else announce({{text="Position unknown.",c=COLORS.YELLOW}})end
  176. end
  177. commandHandlers.fuel = function(u,_)
  178.     local l,lm=turtle.getFuelLevel(),turtle.getFuelLimit()
  179.     announce({{text="Fuel: ",c=COLORS.AQUA},{text=tostring(l)..(lm>0 and(" / "..tostring(lm))or" (Unlimited)"),c=COLORS.WHITE}})
  180. end
  181. commandHandlers.unstuck = function(u,_)
  182.     announce({{text="Unstuck: Attempting to warp home and reset position...",c=COLORS.YELLOW}});botLog(u.." used unstuck command.")
  183.     isDefragRunning=false -- Stop any ongoing defrag
  184.     if ml.h()then announce({{text="Warped home and position reset successfully.",c=COLORS.GREEN}})
  185.     else announce({{text="Failed to warp home. Manual assistance may be required.",c=COLORS.RED}})end
  186. end
  187. --#endregion
  188.  
  189. --#region DefragBot Core Logic
  190. local function handleMoveFailure(actionDescription, criticalMoveFailed)
  191.     botLog("!!! INSIDE handleMoveFailure for: " .. actionDescription)
  192.     announce({{text="CRITICAL FAILURE: Failed to " .. actionDescription .. ". ABORTING DEFRAG CYCLE.", c=COLORS.DARK_RED, b=true}})
  193.     if criticalMoveFailed and turtle.getFuelLevel() < 10 and turtle.getFuelLimit() ~= 0 and not fuelWarningSent then
  194.         announce({{text="HELP: Fuel low ("..turtle.getFuelLevel()..") during critical move! Please refuel and use '@defrag unstuck'.", c=COLORS.RED, b=true}})
  195.         fuelWarningSent = true
  196.     end
  197.     isDefragRunning = false
  198.     botLog("!!! handleMoveFailure set isDefragRunning to false.")
  199. end
  200.  
  201. local function performDefragCycle()
  202.     botLog(">>> performDefragCycle STARTING. isDefragRunning should be true.")
  203.     isDefragRunning = true; fuelWarningSent = false
  204.    
  205.     botLog("Warping home to start defrag cycle...")
  206.     if not ml.h() then
  207.         handleMoveFailure("warp home at cycle start", true)
  208.         botLog(">>> pDC RETURN from initial home warp fail.")
  209.         return
  210.     end
  211.     botLog("Successfully warped home (0,0,0,N).")
  212.    
  213.     local announce_success_initial, announce_error_initial = pcall(announce, {{text="Starting ME Drive Defragmentation Cycle...",c=COLORS.GOLD,b=true}})
  214.     if not announce_success_initial then botLog("!!! Initial announce in performDefragCycle FAILED: " .. tostring(announce_error_initial)) end
  215.    
  216.     botLog(">>> Current pos: X:"..tostring(current_pos.x).." Y:"..tostring(current_pos.y).." Z:"..tostring(current_pos.z).." Dir:"..(current_dir and DIR_NAMES[current_dir]or"Unk"))
  217.    
  218.     botLog("Refueling at home base.")
  219.     ml.refuel()
  220.    
  221.     local initInvItems={};for i=1,15 do if turtle.getItemCount(i)>0 then local d=turtle.getItemDetail(i);table.insert(initInvItems,{slot=i,count=turtle.getItemCount(i),name=(d and d.name or "Unk")})end end
  222.     if #initInvItems>0 then
  223.         announce({{text="Pre-existing items found in inventory. Attempting to place in ME Drives...",c=COLORS.YELLOW}})
  224.         local allPlaced=true
  225.         for itemIdx=#initInvItems,1,-1 do
  226.             local itemD=initInvItems[itemIdx]
  227.             botLog("Attempting to place "..itemD.count.."x "..itemD.name.." from slot "..itemD.slot)
  228.             local curPlaced=false
  229.             for _,driveL in ipairs(ALL_ME_DRIVE_LOCATIONS)do local skipDrive=false
  230.                 if not isDefragRunning then announce({{text="Defrag stopped during pre-item placement.",c=COLORS.YELLOW}});return end
  231.                 if turtle.getItemCount(itemD.slot)==0 then curPlaced=true;break end
  232.                 if turtle.getFuelLevel()<10 and turtle.getFuelLimit()~=0 then handleMoveFailure("move to ME Drive (low fuel, pre-items)",true);return end
  233.                
  234.                 botLog("Moving to "..driveL.name.." to place pre-existing item.")
  235.                 if not ml.moveTo(driveL.x,driveL.y,driveL.z,driveL.dir)then
  236.                     announce({{text="Fail move to ME Drive "..driveL.name.." to place item. Skip drive.",c=COLORS.RED}})
  237.                     skipDrive=true
  238.                 end
  239.                 if not skipDrive then
  240.                     turtle.select(itemD.slot)
  241.                     local dropAtt=turtle.getItemCount(itemD.slot)
  242.                     botLog("Attempting to drop "..dropAtt.."x "..itemD.name.." into "..driveL.name)
  243.                     for _=1,dropAtt do
  244.                         if not turtle.drop()then
  245.                             botLog("Fail drop item "..itemD.name.." into "..driveL.name)
  246.                             break
  247.                         end
  248.                         botLog("Placed 1x "..itemD.name.." into "..driveL.name)
  249.                         if turtle.getItemCount(itemD.slot)==0 then curPlaced=true;break end
  250.                         sleep(0.2)
  251.                     end
  252.                     if curPlaced then break end
  253.                 end
  254.             end
  255.             if not curPlaced then
  256.                 announce({{text="Could not place all "..itemD.count.."x "..itemD.name.." from initial inventory.",c=COLORS.RED}})
  257.                 allPlaced=false
  258.             else
  259.                 botLog("Successfully placed all "..itemD.count.."x "..itemD.name.." from initial inventory.")
  260.             end
  261.         end
  262.         if not allPlaced then announce({{text="WARN: Some pre-existing items could not be placed into ME Drives.",c=COLORS.YELLOW}})
  263.         else botLog("All pre-existing items placed successfully.")end
  264.     end
  265.  
  266.     botLog("Beginning to clear IO Port, Hopper, and Temp Chest...")
  267.     for _,locKey in ipairs({"io_port","hopper","chest_temp_storage"})do local skipClear=false
  268.         if not isDefragRunning then announce({{text="Defrag stopped during location clearing.",c=COLORS.YELLOW}});return end
  269.         local loc=LOCATIONS[locKey]
  270.         botLog("Moving to clear "..loc.name.."...")
  271.         if turtle.getFuelLevel()<10 and turtle.getFuelLimit()~=0 then handleMoveFailure("move to "..loc.name.." (low fuel, clearing)",true);return end
  272.         if not ml.moveTo(loc.x,loc.y,loc.z,loc.dir)then
  273.             announce({{text="Fail move to "..loc.name.." for clearing. Skip.",c=COLORS.RED}})
  274.             skipClear=true
  275.         end
  276.         if not skipClear then
  277.             local suckedC=0
  278.             botLog("Sucking items from "..loc.name)
  279.             for _=1,16*64 do if turtle.suck()then suckedC=suckedC+1 else break end end
  280.             if suckedC>0 then
  281.                 botLog("Sucked "..suckedC.." items from "..loc.name)
  282.                 if locKey~="chest_temp_storage"then
  283.                     local tempL=LOCATIONS.chest_temp_storage
  284.                     botLog("Moving items from "..loc.name.." to "..tempL.name)
  285.                     if turtle.getFuelLevel()<10 and turtle.getFuelLimit()~=0 then handleMoveFailure("move to temp chest (low fuel, clearing)",true);return end
  286.                     if not ml.moveTo(tempL.x,tempL.y,tempL.z,tempL.dir)then
  287.                         announce({{text="Fail move to Temp Chest ("..tempL.name..") with items from "..loc.name..". Items remain in turtle.",c=COLORS.RED}})
  288.                     else
  289.                         botLog("Dropping items from "..loc.name.." into "..tempL.name)
  290.                         for slot=1,15 do if turtle.getItemCount(slot)>0 then turtle.select(slot);turtle.drop()end end
  291.                         botLog("Moved items from "..loc.name.." to temp chest.")
  292.                     end
  293.                 end
  294.             else
  295.                 botLog("No items to suck from "..loc.name)
  296.             end
  297.         end
  298.     end
  299.    
  300.     botLog("Stage 2: Processing ME Drive Bays...")
  301.     for i,driveL in ipairs(ALL_ME_DRIVE_LOCATIONS)do local skipDriveLoop=false
  302.         if not isDefragRunning then announce({{text="Defrag stopped during ME Drive processing.",c=COLORS.YELLOW}});return end
  303.         botLog("Processing ME Drive "..i.."/"..#ALL_ME_DRIVE_LOCATIONS.." ("..driveL.name..")")
  304.        
  305.         if turtle.getFuelLevel()<10 and turtle.getFuelLimit()~=0 then handleMoveFailure("move to ME Drive "..driveL.name.." (low fuel)",true);return end
  306.         if not ml.moveTo(driveL.x,driveL.y,driveL.z,driveL.dir)then
  307.             announce({{text="Fail move to ME Drive "..driveL.name..". Skipping this drive.",c=COLORS.RED}})
  308.             skipDriveLoop=true
  309.         end
  310.        
  311.         if not skipDriveLoop then
  312.             local disksToProc={}
  313.             botLog("Retrieving disks from ME Drive "..driveL.name.."...")
  314.  
  315.             local successfullySuckedCount = 0
  316.             for suck_idx = 1, 15 do
  317.                 if not turtle.suck() then
  318.                     botLog("turtle.suck() from ME Drive returned false. Attempt: " .. suck_idx)
  319.                     break
  320.                 else
  321.                     successfullySuckedCount = successfullySuckedCount + 1
  322.                     botLog("turtle.suck() from ME Drive successful. Disk " .. successfullySuckedCount .. " collected this batch.")
  323.                     sleep(0.1)
  324.                 end
  325.             end
  326.             botLog("Finished sucking attempt from ME Drive "..driveL.name..". Succeeded " .. successfullySuckedCount .. " times.")
  327.  
  328.             for slot = 1, 15 do
  329.                 if turtle.getItemCount(slot) > 0 then
  330.                     local itemDetail = turtle.getItemDetail(slot)
  331.                     local itemName = (itemDetail and itemDetail.name or "Unknown Item")
  332.                     local itemCount = turtle.getItemCount(slot)
  333.                     table.insert(disksToProc, {slot = slot, name = itemName, count = itemCount})
  334.                     botLog("Inventoried from ME Drive: Slot " .. slot .. " has " .. itemCount .. "x " .. itemName)
  335.                 end
  336.             end
  337.            
  338.             if #disksToProc == 0 then
  339.                 botLog("No disks found/retrieved in this ME Drive: "..driveL.name)
  340.                 skipDriveLoop = true
  341.             end
  342.  
  343.             if not skipDriveLoop then
  344.                 local totalDisks=0; for _,d in ipairs(disksToProc) do totalDisks=totalDisks+d.count end
  345.                 botLog("Collected "..totalDisks.." disk(s) total from "..driveL.name)
  346.                
  347.                 local chestL=LOCATIONS.chest_temp_storage
  348.                 if turtle.getFuelLevel()<10 and turtle.getFuelLimit()~=0 then handleMoveFailure("move to temp chest (low fuel, ME disks)",true);return end
  349.                
  350.                 botLog("Moving to "..chestL.name.." to drop disks.")
  351.                 if not ml.moveTo(chestL.x,chestL.y,chestL.z,chestL.dir)then handleMoveFailure("move to temp chest ("..chestL.name..") with ME disks",false);return end
  352.                
  353.                 botLog("Dropping "..totalDisks.." disk(s) to "..chestL.name)
  354.                 for _,diskD in ipairs(disksToProc) do
  355.                     turtle.select(diskD.slot)
  356.                     if not turtle.drop() then
  357.                         announce({{text="Fail drop disk '",c=COLORS.RED},{text=diskD.name,c=COLORS.YELLOW},{text="' from slot "..diskD.slot.." into "..chestL.name,c=COLORS.RED}})
  358.                         botLog("Failed to drop disk " .. diskD.name .. " from slot " .. diskD.slot .. " into chest.")
  359.                     else
  360.                         botLog("Dropped disk " .. diskD.name .. " from slot " .. diskD.slot .. " into chest.")
  361.                     end
  362.                 end
  363.                
  364.                 local ioL=LOCATIONS.io_port
  365.                 if turtle.getFuelLevel()<10 and turtle.getFuelLimit()~=0 then handleMoveFailure("move to IO port (low fuel)",true);return end
  366.                
  367.                 botLog("Moving to "..ioL.name.." for disk processing.")
  368.                 if not ml.moveTo(ioL.x,ioL.y,ioL.z,ioL.dir)then handleMoveFailure("move to IO port ("..ioL.name..")",false);return end
  369.                
  370.                 -- VVVVVV IO PORT WAIT LOGIC (ANNOUNCE REMOVED) VVVVVV
  371.                 botLog("Waiting for "..totalDisks.." processed disk(s) from IO Port "..ioL.name.."...")
  372.                 local procRet=0
  373.                 local noDiskTries=0
  374.                 local MAX_NO_DISK_TRIES=100
  375.                
  376.                 while procRet<totalDisks and noDiskTries<MAX_NO_DISK_TRIES do
  377.                     if not isDefragRunning then announce({{text="Defrag stopped while waiting for IO Port.",c=COLORS.YELLOW}});return end
  378.                     local retrievedThisCycle=false
  379.                     for slot=1,15 do
  380.                         if turtle.getItemCount(slot)==0 then
  381.                             turtle.select(slot)
  382.                             if turtle.suck()then
  383.                                 procRet=procRet+1
  384.                                 local itemDetail = turtle.getItemDetail(slot)
  385.                                 local itemName = (itemDetail and itemDetail.name or "Unknown Disk")
  386.                                 botLog("Retrieved processed disk "..procRet.."/"..totalDisks.." ('"..itemName.."') from IO Port into slot "..slot)
  387.                                 noDiskTries=0
  388.                                 retrievedThisCycle=true
  389.                                 break
  390.                             end
  391.                         end
  392.                     end
  393.                     if procRet>=totalDisks then break end
  394.                    
  395.                     if not retrievedThisCycle then
  396.                         noDiskTries=noDiskTries+1
  397.                         botLog("IO Port waiting... (No disk retrieved this try. Attempt "..noDiskTries.."/"..MAX_NO_DISK_TRIES..")")
  398.                     end
  399.                     sleep(1)
  400.                 end
  401.                 -- ^^^^^^ END OF IO PORT WAIT LOGIC ^^^^^^
  402.  
  403.                 if procRet<totalDisks then
  404.                     announce({{text="Timeout waiting for IO Port: Retrieved only "..procRet.."/"..totalDisks.." disk(s). Proceeding.",c=COLORS.RED}})
  405.                 else
  406.                     botLog("Successfully retrieved all "..totalDisks.." processed disk(s) from IO port.")
  407.                 end
  408.  
  409.                 if procRet==0 and totalDisks>0 then
  410.                     announce({{text="No disks retrieved from IO Port after waiting. Skipping return to ME Drive "..driveL.name..".",c=COLORS.RED}})
  411.                     skipDriveLoop=true
  412.                 end
  413.                
  414.                 if not skipDriveLoop then
  415.                     botLog("Preparing to return "..procRet.." disk(s) to ME Drive "..driveL.name)
  416.                     if turtle.getFuelLevel()<10 and turtle.getFuelLimit()~=0 then handleMoveFailure("move home (low fuel, pre-return refuel)",true);return end
  417.                    
  418.                     botLog("Moving to home for refuel before returning disks.")
  419.                     if not ml.moveTo(LOCATIONS.home.x,LOCATIONS.home.y,LOCATIONS.home.z,LOCATIONS.home.dir)then
  420.                         announce({{text="Fail move home for refuel before returning disks. Disks remain in turtle.",c=COLORS.RED}})
  421.                     else
  422.                         ml.refuel()
  423.                     end
  424.  
  425.                     if turtle.getFuelLevel()<10 and turtle.getFuelLimit()~=0 then handleMoveFailure("move back to ME Drive "..driveL.name.." (low fuel, post-refuel)",true);return end
  426.                    
  427.                     botLog("Moving back to ME Drive "..driveL.name.." to return disks.")
  428.                     if not ml.moveTo(driveL.x,driveL.y,driveL.z,driveL.dir)then
  429.                         announce({{text="Fail to return to ME Drive "..driveL.name..". Processed disks remain in turtle.",c=COLORS.RED}})
  430.                         skipDriveLoop=true
  431.                     end
  432.                    
  433.                     if not skipDriveLoop then
  434.                         botLog("Returning "..procRet.." processed disk(s) to "..driveL.name)
  435.                         local totalReturnedToDrive = 0
  436.                         for slot=1,15 do
  437.                             if turtle.getItemCount(slot)>0 then
  438.                                 turtle.select(slot)
  439.                                 local countInSlot=turtle.getItemCount(slot)
  440.                                 local droppedCount=0
  441.                                 local itemDetail = turtle.getItemDetail(slot)
  442.                                 local itemName = (itemDetail and itemDetail.name or "Unknown Disk")
  443.                                 botLog("Attempting to return "..countInSlot.."x "..itemName.." from slot "..slot.." to "..driveL.name)
  444.                                 for _=1,countInSlot do
  445.                                     if turtle.drop()then
  446.                                         droppedCount=droppedCount+1
  447.                                     else
  448.                                         botLog("Failed to drop disk from slot "..slot.." into ME Drive "..driveL.name)
  449.                                         break
  450.                                     end
  451.                                 end
  452.                                 if droppedCount>0 then
  453.                                     botLog("Returned "..droppedCount.." disk(s) ('"..itemName.."') from slot "..slot.." to "..driveL.name)
  454.                                     totalReturnedToDrive = totalReturnedToDrive + droppedCount
  455.                                 end
  456.                                 if droppedCount<countInSlot then
  457.                                     announce({{text="Failed to return all "..countInSlot.." disk(s) from slot "..slot.." to ME Drive "..driveL.name..". Returned "..droppedCount..".",c=COLORS.RED}})
  458.                                 end
  459.                             end
  460.                         end
  461.                         botLog("Finished returning disks to ME Drive "..driveL.name..". Total returned this drive: " .. totalReturnedToDrive .. " of " .. procRet .. " retrieved from IO.")
  462.                         if totalReturnedToDrive < procRet then
  463.                              announce({{text="WARN: Not all processed disks ("..totalReturnedToDrive.."/"..procRet..") were returned to ME Drive "..driveL.name, c=COLORS.YELLOW}})
  464.                         end
  465.                     end
  466.                 end
  467.             end
  468.         end
  469.         botLog("Finished processing ME Drive "..driveL.name)
  470.         sleep(0.5)
  471.     end
  472.    
  473.     -- VVVVVV MODIFIED END OF CYCLE ACTIONS VVVVVV
  474.     botLog("Defragmentation cycle nearing completion. Returning home and refueling...")
  475.     if not ml.h() then
  476.         announce({{text="WARN: Failed to warp home at end of defrag cycle. Current position may be off.", c=COLORS.YELLOW}})
  477.     else
  478.         botLog("Successfully warped home at end of cycle.")
  479.     end
  480.     ml.refuel()
  481.     botLog("Final refuel complete. Fuel: " .. turtle.getFuelLevel())
  482.     announce({{text="Defragmentation Cycle Complete!",c=COLORS.GOLD,b=true}})
  483.     -- ^^^^^^ END OF MODIFIED END OF CYCLE ACTIONS ^^^^^^
  484.     isDefragRunning=false
  485. end
  486.  
  487. commandHandlers.startdefrag=function(u,_)
  488.     botLog(">>> startdefrag command called. Current isDefragRunning: " .. tostring(isDefragRunning))
  489.     if isDefragRunning then announce({{text="Defrag already running.",c=COLORS.YELLOW}}); botLog(">>> startdefrag: Already running, exiting."); return end;
  490.     botLog(u.." started defrag.")
  491.     performDefragCycle()
  492.     botLog(">>> startdefrag: performDefragCycle completed or errored out.")
  493. end
  494. commandHandlers.stopdefrag=function(u,_)
  495.     if isDefragRunning then
  496.         announce({{text="Attempting to signal stop to defrag process...",c=COLORS.YELLOW}})
  497.         isDefragRunning=false;
  498.         botLog(u.." requested stop. isDefragRunning set to false.")
  499.     else
  500.         announce({{text="Defrag not running.",c=COLORS.GRAY}})
  501.     end
  502. end
  503. --#endregion
  504.  
  505. --#region DefragBot Main Loop
  506. local function run()
  507.     if DEBUG_LOGGING_ENABLED then
  508.         local file, err = fs.open(LOG_FILE_NAME, "w")
  509.         if file then file.write(string.format("[%s] %s Log Initialized.\n", os.date("%Y-%m-%d %H:%M:%S"), CHAT_BOT_NAME)); file.write("============================================================\n"); file.close()
  510.         else print("LOGGING ERROR: Could not initialize "..LOG_FILE_NAME..": "..tostring(err)) end
  511.     end
  512.     term.clear();term.setCursorPos(1,1);
  513.    
  514.     chatBox = peripheral.find(CHAT_BOX_PERIPHERAL_NAME)
  515.     botLog("DefragBot initializing...")
  516.     botLog("Initial ChatBox peripheral check: " .. tostring(chatBox))
  517.     if chatBox and chatBox.sendFormattedMessage and chatBox.sendFormattedMessageToPlayer then
  518.         chatBoxFunctional = true
  519.         botLog("ChatBox determined to be FUNCTIONAL (key methods exist via direct check).")
  520.     else
  521.         if chatBox then
  522.             botLog("ChatBox found, but MISSING ESSENTIAL sendFormattedMessage(ToPlayer) methods. Marking as non-functional.")
  523.         else
  524.             botLog("ChatBox peripheral IS NIL at startup. Marking as non-functional.")
  525.         end
  526.         chatBoxFunctional = false
  527.     end
  528.    
  529.     botLog(CHAT_BOT_NAME.." online. MoveLib init complete.")
  530.     print(CHAT_BOT_NAME.." online. '"..COMMAND_PREFIX.." help' or '@all'.")
  531.    
  532.     local initial_announce_success = announce({{text=CHAT_BOT_NAME.." online, ready for defrag duties!",c=COLORS.GREEN}})
  533.     if not initial_announce_success then
  534.         botLog("Initial online announcement FAILED. Chat will be console/log only if ChatBox is non-functional.")
  535.     end
  536.    
  537.     while true do
  538.         local eventData={os.pullEvent()}
  539.         local eventType=eventData[1]
  540.  
  541.         if not isDefragRunning then
  542.             if eventType=="chat"then local eU,eM=eventData[2],eventData[3]
  543.                 if eM then
  544.                     if string.lower(eM)=="@all"then
  545.                         botLog("@all from "..eU)
  546.                         announce({{text="Use '",c=COLORS.GREEN},{text=COMMAND_PREFIX.." help",c=COLORS.AQUA},{text="' for my commands.",c=COLORS.GREEN}})
  547.                     elseif string.sub(eM,1,#COMMAND_PREFIX)==COMMAND_PREFIX then
  548.                         botLog("Chat cmd from "..eU..": "..eM)
  549.                         local p={};for pt in string.gmatch(eM,"[^%s]+")do table.insert(p,pt)end
  550.                         local cn=""; if p[2]then cn=string.lower(p[2])end -- cn is the command name, e.g. "help", "cmd"
  551.                         local ca={};for i=3,#p do table.insert(ca,p[i])end -- ca are arguments to the command
  552.                        
  553.                         if commandHandlers[cn]then
  554.                             commandHandlers[cn](eU,ca)
  555.                         elseif cn~=""then
  556.                             -- VVVVVV MODIFIED UNKNOWN COMMAND SUGGESTION VVVVVV
  557.                             if p[2] and string.find(p[2], "%(") and string.find(p[2], "%)") then
  558.                                 -- p[2] is the original second word, before lowercasing by cn=string.lower(p[2])
  559.                                 announce({{text="Unknown Command: '",c=COLORS.RED},{text=p[2],c=COLORS.YELLOW},{text="'. Did you mean to use '",c=COLORS.RED}, {text=COMMAND_PREFIX .. " cmd " .. table.concat(ca, " ", 2), c=COLORS.AQUA},{text="'?",c=COLORS.RED}})
  560.                                 -- Note: We reconstruct the arguments here. If p[2] was "ml.foo()" and there were args after, this gets a bit tricky.
  561.                                 -- A simpler message if they just put a function as p[2]:
  562.                                 announce({{text="Unknown Command: '",c=COLORS.RED},{text=p[2],c=COLORS.YELLOW},{text="'. If this is a Lua function, try: '",c=COLORS.RED},{text=COMMAND_PREFIX.." cmd "..p[2],c=COLORS.AQUA},{text="'",c=COLORS.RED}})
  563.                             else
  564.                                 announce({{text="Unknown Command: '",c=COLORS.RED},{text=cn,c=COLORS.YELLOW},{text="'. Try '",c=COLORS.RED},{text=COMMAND_PREFIX .. " help",c=COLORS.AQUA},{text="'.",c=COLORS.RED}})
  565.                             end
  566.                             -- ^^^^^^ END OF MODIFIED UNKNOWN COMMAND SUGGESTION ^^^^^^
  567.                         end
  568.                     end
  569.                 end
  570.             elseif eventType=="terminate"then
  571.                 botLog("Terminate. Shutdown.");isDefragRunning=false;
  572.                 if chatBoxFunctional then announce({{text=CHAT_BOT_NAME.." shut down.",c=COLORS.YELLOW}}) end;
  573.                 return
  574.             end
  575.         elseif eventType == "terminate" then
  576.             botLog("Terminate received during defrag. Shutting down.");isDefragRunning=false;
  577.             if chatBoxFunctional then announce({{text=CHAT_BOT_NAME.." shut down (during defrag).",c=COLORS.YELLOW}}) end;
  578.             return
  579.         else
  580.             botLog("Event received during defrag (ignored by main loop): " .. eventType)
  581.         end
  582.     end
  583. end
  584. run()
  585. --#endregion
Add Comment
Please, Sign In to add comment