Myros27

disenchantmentBot

May 20th, 2025 (edited)
28
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 37.67 KB | None | 0 0
  1. --[[
  2.     DisenchantBot v1.1 - Removed parallel.call
  3.     Implements the disenchanting loop as specified, using main event loop for cycle management.
  4. ]]
  5.  
  6. --#region Configuration
  7. local CHAT_BOX_PERIPHERAL_NAME = "chatBox"
  8. local COMMAND_PREFIX = "@disenchant"
  9. local CHAT_BOT_NAME = "DisenchantBot"
  10. local CHAT_BOT_BRACKETS = "[]"
  11. local CHAT_BOT_BRACKET_COLOR = "&3" -- Cyan
  12.  
  13. local DEBUG_LOGGING_ENABLED = true
  14. local LOG_FILE_NAME = "disenchant.log"
  15. --#endregion
  16.  
  17. --#region Global State
  18. local chatBox = nil
  19. local chatBoxFunctional = true
  20. local isDisenchantRunning = false
  21. local fuelWarningSent = false
  22. --#endregion
  23.  
  24. --#region Logger Setup
  25. 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
  26. local function botLog(msg)local fm="["..CHAT_BOT_NAME.."-Debug] "..msg; print(fm); writeToLogFile(fm)end
  27. local function moveLog(msg)local fm="[MoveLib-Debug] "..msg; print(fm); writeToLogFile(fm)end
  28. --#endregion
  29.  
  30. --#region Movement Library Code (Integrated - Assumed same as v1.7 from DefragBot)
  31. 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();
  32. --#endregion End of Integrated Movement Library
  33.  
  34. --#region DisenchantBot Peripherals and Helpers
  35. local function sendFormattedChat(messageComponents)
  36.     local plainText = ""
  37.     if type(messageComponents) == "table" then
  38.         for _,c_comp in ipairs(messageComponents) do
  39.             plainText = plainText .. (type(c_comp) == "table" and c_comp.text or tostring(c_comp) or "")
  40.         end
  41.     elseif messageComponents ~= nil then plainText = tostring(messageComponents)
  42.     else plainText = "nil_message_components_to_sendFormattedChat" end
  43.  
  44.     if not chatBoxFunctional or not chatBox then
  45.         botLog("[NoChat] " .. plainText);
  46.         return false
  47.     end
  48.  
  49.     local jsonMessage_success, jsonMessage = pcall(textutils.serialiseJSON, messageComponents)
  50.     if not jsonMessage_success then
  51.         botLog("!!! textutils.serialiseJSON FAILED: " .. tostring(jsonMessage) .. " -- Original: " .. textutils.serialize(messageComponents,{compact=true,max_depth=2}))
  52.         print("ERROR: Could not serialize chat message!"); return false
  53.     end
  54.  
  55.     local peripheral_call_success, peripheral_error = pcall(function()
  56.         return chatBox.sendFormattedMessage(jsonMessage, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  57.     end)
  58.  
  59.     if not peripheral_call_success then
  60.         botLog("!!! chatBox.sendFormattedMessage FAILED: " .. tostring(peripheral_error) .. " -- JSON was: " .. jsonMessage)
  61.         print("ERROR: Failed to send formatted message via chatBox: " .. tostring(peripheral_error))
  62.         return false
  63.     end
  64.     return true
  65. end
  66.  
  67. local function announce(messageComponents)
  68.     local success = sendFormattedChat(messageComponents)
  69.     if not success then
  70.         botLog("Announce: sendFormattedChat failed for: " .. textutils.serialize(messageComponents, {compact=true, max_depth=2}))
  71.     end
  72.     sleep(0.3)
  73.     return success
  74. end
  75.  
  76. local COLORS={GOLD="gold",AQUA="aqua",GRAY="gray",RED="red",GREEN="green",YELLOW="yellow",WHITE="white",DARK_RED="dark_red",DARK_GRAY="dark_gray"}
  77.  
  78. local function critical_error_handler(message, doTurnLeft)
  79.     announce({{text = "CRITICAL ERROR: " .. message .. " Halting " .. CHAT_BOT_NAME .. ".", c = COLORS.DARK_RED, b = true}})
  80.     botLog("CRITICAL ERROR: " .. message)
  81.     if doTurnLeft then
  82.         ml.turnLeft()
  83.     end
  84.     isDisenchantRunning = false
  85. end
  86.  
  87. local function check_stop_signal()
  88.     if not isDisenchantRunning then
  89.         botLog("Stop signal detected during cycle.")
  90.         return true
  91.     end
  92.     sleep(0) -- Yield to allow main event loop to process commands
  93.     return false
  94. end
  95. --#endregion
  96.  
  97. --#region DisenchantBot Command Handlers
  98. local commandHandlers = {}
  99.  
  100. commandHandlers.help = function(u,_)
  101.     announce({{text="--- "..CHAT_BOT_NAME.." Cmds ("..COMMAND_PREFIX..") ---",c=COLORS.GOLD,b=true}})
  102.     announce({{text=COMMAND_PREFIX.." help",c=COLORS.AQUA},{text=" - Shows this help message.",c=COLORS.GRAY}})
  103.     announce({{text=COMMAND_PREFIX.." start",c=COLORS.AQUA},{text=" - Starts the disenchanting loop.",c=COLORS.GRAY}})
  104.     announce({{text=COMMAND_PREFIX.." stop",c=COLORS.AQUA},{text=" - Stops the current disenchanting loop.",c=COLORS.GRAY}})
  105.     announce({{text=COMMAND_PREFIX.." cmd <lua_code>",c=COLORS.AQUA},{text=" - Executes Lua code (e.g., ml.forward()).",c=COLORS.GRAY}})
  106.     announce({{text=COMMAND_PREFIX.." pos",c=COLORS.AQUA},{text=" - Shows current position and direction.",c=COLORS.GRAY}})
  107.     announce({{text=COMMAND_PREFIX.." fuel",c=COLORS.AQUA},{text=" - Shows current fuel level.",c=COLORS.GRAY}})
  108.     announce({{text=COMMAND_PREFIX.." unstuck",c=COLORS.AQUA},{text=" - Warps home and resets position (stops current cycle).",c=COLORS.GRAY}})
  109. end
  110.  
  111. commandHandlers.cmd = function(u,a)
  112.     if #a == 0 then
  113.         announce({{text="Usage: "..COMMAND_PREFIX.." cmd <lua_code_to_run>",c=COLORS.YELLOW}})
  114.         announce({{text="Try '",c=COLORS.GRAY},{text=COMMAND_PREFIX.." cmd help",c=COLORS.AQUA},{text="' for more info.",c=COLORS.GRAY}})
  115.         return
  116.     end
  117.  
  118.     if #a == 1 and string.lower(a[1]) == "help" then
  119.         announce({{text="--- "..COMMAND_PREFIX.." cmd Help ---",c=COLORS.GOLD,b=true}})
  120.         announce({{text="Usage: ",c=COLORS.AQUA},{text=COMMAND_PREFIX.." cmd <lua_code_to_run>",c=COLORS.WHITE}})
  121.         announce({{text="Executes the provided Lua code. You can use 'ml.' to access Movement Library functions.",c=COLORS.GRAY}})
  122.         announce({{text="Examples:",c=COLORS.AQUA}})
  123.         announce({{text="  "..COMMAND_PREFIX.." cmd ml.moveTo(0,0,0,'N')",c=COLORS.WHITE}})
  124.         announce({{text="  "..COMMAND_PREFIX.." cmd ml.turnRight()",c=COLORS.WHITE}})
  125.         announce({{text="  "..COMMAND_PREFIX.." cmd return turtle.getFuelLevel()",c=COLORS.WHITE}})
  126.         announce({{text="  "..COMMAND_PREFIX.." cmd turtle.dig()",c=COLORS.WHITE}})
  127.         announce({{text="Note: The Lua code itself is case-sensitive.",c=COLORS.YELLOW}})
  128.         announce({{text="The command must return a value to see a result in chat, otherwise it's 'nil'.",c=COLORS.GRAY}})
  129.         return
  130.     end
  131.  
  132.     local cs=table.concat(a," ")
  133.     botLog("User "..u.." executing raw command: "..cs)
  134.     local fn,e=load("return "..cs,"raw_user_command","t",{ml=ml, turtle=turtle, peripheral=peripheral, os=os, fs=fs, textutils=textutils, sleep=sleep, print=print, announce=announce, COLORS=COLORS})
  135.  
  136.     if not fn then
  137.         announce({{text="Error parsing command: ",c=COLORS.RED},{text=tostring(e),c=COLORS.YELLOW}})
  138.         botLog("Parse error for raw command '"..cs.."': "..tostring(e))
  139.         return
  140.     end
  141.  
  142.     local s,r=pcall(fn)
  143.     if s then
  144.         announce({{text="Cmd executed. Result: ",c=COLORS.GREEN},{text=tostring(r),c=COLORS.WHITE}})
  145.         botLog("Raw command '"..cs.."' result: "..tostring(r))
  146.     else
  147.         announce({{text="Error executing command: ",c=COLORS.RED},{text=tostring(r),c=COLORS.YELLOW}})
  148.         botLog("Execution error for raw command '"..cs.."': "..tostring(r))
  149.     end
  150. end
  151.  
  152. commandHandlers.pos = function(u,_)
  153.     local pD=ml.getPosition(); local dNS=ml.getDirectionName()
  154.     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}})
  155.     else announce({{text="Position unknown.",c=COLORS.YELLOW}})end
  156. end
  157.  
  158. commandHandlers.fuel = function(u,_)
  159.     local l,lm=turtle.getFuelLevel(),turtle.getFuelLimit()
  160.     announce({{text="Fuel: ",c=COLORS.AQUA},{text=tostring(l)..(lm>0 and(" / "..tostring(lm))or" (Unlimited)"),c=COLORS.WHITE}})
  161. end
  162.  
  163. commandHandlers.unstuck = function(u,_)
  164.     announce({{text="Unstuck: Attempting to warp home and reset position...",c=COLORS.YELLOW}});botLog(u.." used unstuck command.")
  165.     isDisenchantRunning = false
  166.     if ml.home()then announce({{text="Warped home and position reset successfully.",c=COLORS.GREEN}})
  167.     else announce({{text="Failed to warp home. Manual assistance may be required.",c=COLORS.RED}})end
  168. end
  169. --#endregion
  170.  
  171. --#region DisenchantBot Core Logic
  172.  
  173. function performSingleDisenchantCycle()
  174.     botLog("== Starting new Disenchant Cycle ==")
  175.     if check_stop_signal() then return "stopped_by_user" end
  176.  
  177.     botLog("Step 1: Warping home.")
  178.     if not ml.home() then critical_error_handler("Failed to warp home at cycle start.", false); return "error" end
  179.     announce({{text="Warped home. Current orientation: North.", c=COLORS.GRAY}})
  180.     if check_stop_signal() then return "stopped_by_user" end
  181.  
  182.     botLog("Step 2: Dropping all inventory items.")
  183.     for i = 1, 16 do
  184.         turtle.select(i)
  185.         if turtle.getItemCount(i) > 0 then
  186.             botLog("Dropping items from slot " .. i)
  187.             turtle.drop()
  188.         end
  189.     end
  190.     if check_stop_signal() then return "stopped_by_user" end
  191.  
  192.     botLog("Step 3: Checking fuel.")
  193.     local fuelLevel = turtle.getFuelLevel()
  194.     local fuelLimit = turtle.getFuelLimit()
  195.     if fuelLimit > 0 and (fuelLevel / fuelLimit) < 0.50 then
  196.         announce({{text="Fuel under 50% ("..fuelLevel.."/"..fuelLimit.."). Refueling...", c=COLORS.YELLOW}})
  197.         botLog("Fuel low. Attempting to suck, refuel, drop excess.")
  198.         turtle.select(1)
  199.         if not turtle.suck() then
  200.             announce({{text="Failed to suck fuel item.", c=COLORS.RED}})
  201.             botLog("Failed to suck fuel from below.")
  202.         else
  203.             botLog("Sucked an item, attempting to refuel.")
  204.             if not turtle.refuel() then
  205.                  announce({{text="Refuel failed with sucked item. Item might not be fuel.", c=COLORS.RED}})
  206.                  botLog("turtle.refuel() failed.")
  207.             else
  208.                 botLog("Refuel successful. Current Fuel: " .. turtle.getFuelLevel())
  209.             end
  210.             turtle.drop()
  211.             botLog("Dropped item from slot 1 after refuel attempt.")
  212.         end
  213.     else
  214.         botLog("Fuel is OK ("..fuelLevel.."/"..fuelLimit.."). Skipping refuel step.")
  215.     end
  216.     if check_stop_signal() then return "stopped_by_user" end
  217.  
  218.     botLog("Step 4: Turning right (to East).")
  219.     if not ml.turnRight() then critical_error_handler("Failed to turn right.", false); return "error" end
  220.     if check_stop_signal() then return "stopped_by_user" end
  221.  
  222.     botLog("Step 5: Sucking up to 16 items (1 by 1) from East.")
  223.     local itemsSuckedInStep5 = 0
  224.     for i = 1, 16 do
  225.         local emptySlotFound = false
  226.         local originalSlot = turtle.getSelectedSlot()
  227.         for slotIdx = 1, 16 do
  228.             if turtle.getItemCount(slotIdx) == 0 then
  229.                 turtle.select(slotIdx)
  230.                 emptySlotFound = true
  231.                 break
  232.             end
  233.         end
  234.         if not emptySlotFound then botLog("No empty slot to suck item into."); break end
  235.        
  236.         if turtle.suck(1) then itemsSuckedInStep5 = itemsSuckedInStep5 + 1; botLog("Sucked item " .. itemsSuckedInStep5 .. " into slot " .. turtle.getSelectedSlot())
  237.         else turtle.select(originalSlot); botLog("Failed to suck more items after " .. itemsSuckedInStep5 .. " items."); break end
  238.     end
  239.     announce({{text="Sucked "..itemsSuckedInStep5.." item(s) for processing.", c=COLORS.GRAY}})
  240.     if check_stop_signal() then return "stopped_by_user" end
  241.  
  242.     botLog("Step 6: Checking inventory for non-stackable items.")
  243.     local slotsWithNonStackableItems = {}
  244.     for slot = 1, 16 do
  245.         turtle.select(slot)
  246.         if turtle.getItemCount(slot) > 0 then
  247.             local space = turtle.getItemSpace(slot)
  248.             if space == 0 then table.insert(slotsWithNonStackableItems, slot); botLog("Slot " .. slot .. ": Non-stackable item found.")
  249.             elseif space == 64 then critical_error_handler("Inventory check contradiction in slot " .. slot .. ".", true); return "error"
  250.             else critical_error_handler("Inventory check: Slot " .. slot .. " has unexpected item (space=" .. space .. ").", true); return "error"
  251.             end
  252.         end
  253.     end
  254.     local expectedNonStackableCount = #slotsWithNonStackableItems
  255.     botLog("Found " .. expectedNonStackableCount .. " non-stackable item(s) in slots: " .. table.concat(slotsWithNonStackableItems, ", "))
  256.  
  257.     if expectedNonStackableCount == 0 then
  258.         if itemsSuckedInStep5 > 0 then announce({{text="No valid items out of "..itemsSuckedInStep5.." sucked. Restarting.", c=COLORS.YELLOW}})
  259.         else announce({{text="No items found to process. Restarting.", c=COLORS.YELLOW}}) end
  260.         if not ml.turnLeft() then critical_error_handler("Failed to turn left before wait.", false); return "error" end
  261.         botLog("Waiting 60 seconds as no items to process.")
  262.         for _=1,60 do if check_stop_signal() then return "stopped_by_user" end; sleep(1) end
  263.         return "continue_looping"
  264.     end
  265.     announce({{text="Processing "..expectedNonStackableCount.." non-stackable item(s).", c=COLORS.AQUA}})
  266.     if check_stop_signal() then return "stopped_by_user" end
  267.  
  268.     botLog("Step 7: Turning right (to South) to face 'tryOutputchest'.")
  269.     if not ml.turnRight() then critical_error_handler("Failed to turn right towards tryOutputchest.", false); return "error" end
  270.    
  271.     botLog("Dropping " .. expectedNonStackableCount .. " items into tryOutputchest.")
  272.     for _, slotIdx in ipairs(slotsWithNonStackableItems) do
  273.         turtle.select(slotIdx)
  274.         if turtle.getItemCount(slotIdx) > 0 then
  275.             if not turtle.drop() then critical_error_handler("Failed to drop item from slot " .. slotIdx .. " into tryOutputchest.", false); return "error" end
  276.         end
  277.     end
  278.     slotsWithNonStackableItems = {}
  279.  
  280.     botLog("Waiting 5 seconds after dropping items.")
  281.     for _=1,5 do if check_stop_signal() then return "stopped_by_user" end; sleep(1) end
  282.  
  283.     local itemsLeftToProcessCount = expectedNonStackableCount
  284.     local itemsSuccessfullyProcessedFlag = false
  285.     botLog("Starting extractor check loop. Expecting " .. itemsLeftToProcessCount .. " items back initially.")
  286.     while itemsLeftToProcessCount > 0 do
  287.         if check_stop_signal() then return "stopped_by_user" end
  288.         botLog("Attempting to suck back up to " .. itemsLeftToProcessCount .. " item(s).")
  289.         local itemsSuckedThisPass = 0
  290.         local currentPassSuckedSlots = {}
  291.  
  292.         for _ = 1, itemsLeftToProcessCount do
  293.             local emptySlotFound = false; local originalSlot = turtle.getSelectedSlot()
  294.             for slotIdx = 1, 16 do if turtle.getItemCount(slotIdx) == 0 then turtle.select(slotIdx); emptySlotFound = true; break end end
  295.             if not emptySlotFound then botLog("Extractor loop: Inventory full."); turtle.select(originalSlot); break end
  296.  
  297.             if turtle.suck(1) then itemsSuckedThisPass = itemsSuckedThisPass + 1; table.insert(currentPassSuckedSlots, turtle.getSelectedSlot()); botLog("Extractor loop: Sucked item " .. itemsSuckedThisPass .. " into slot " .. turtle.getSelectedSlot())
  298.             else turtle.select(originalSlot); botLog("Extractor loop: Failed to suck more after " .. itemsSuckedThisPass .. " items."); break end
  299.         end
  300.        
  301.         local validNonStackableItemsAfterSuck = 0; local slotsWithValidItemsThisPass = {}
  302.         for _, slotIdx in ipairs(currentPassSuckedSlots) do
  303.             turtle.select(slotIdx)
  304.             if turtle.getItemCount(slotIdx) > 0 and turtle.getItemSpace(slotIdx) == 0 then validNonStackableItemsAfterSuck = validNonStackableItemsAfterSuck + 1; table.insert(slotsWithValidItemsThisPass, slotIdx)
  305.             elseif turtle.getItemCount(slotIdx) > 0 then botLog("Warning: Extractor returned stackable/partial to slot " .. slotIdx); validNonStackableItemsAfterSuck = validNonStackableItemsAfterSuck + 1; table.insert(slotsWithValidItemsThisPass, slotIdx) end
  306.         end
  307.         botLog("Extractor loop: Got back " .. validNonStackableItemsAfterSuck .. " valid non-stackable items this pass.")
  308.  
  309.         if validNonStackableItemsAfterSuck < itemsLeftToProcessCount then
  310.             itemsSuccessfullyProcessedFlag = true
  311.             local processedCountThisPass = itemsLeftToProcessCount - validNonStackableItemsAfterSuck
  312.             announce({{text="Extractor processed "..processedCountThisPass.." item(s). " .. validNonStackableItemsAfterSuck .. " remaining.", c=COLORS.GRAY}})
  313.             botLog("Dropping remaining " .. validNonStackableItemsAfterSuck .. " items.")
  314.             for _, slotIdx in ipairs(slotsWithValidItemsThisPass) do turtle.select(slotIdx); if turtle.getItemCount(slotIdx) > 0 then turtle.drop() end end
  315.             botLog("Waiting 20 seconds.")
  316.             for _=1,20 do if check_stop_signal() then return "stopped_by_user" end; sleep(1) end
  317.             itemsLeftToProcessCount = validNonStackableItemsAfterSuck
  318.             if itemsLeftToProcessCount == 0 then announce({{text="All items processed by extractor.", c=COLORS.GREEN}}); botLog("All items processed."); break end
  319.         else
  320.             if validNonStackableItemsAfterSuck > itemsLeftToProcessCount then announce({{text="Warning: Got "..validNonStackableItemsAfterSuck.." items, expected "..itemsLeftToProcessCount..".", c=COLORS.YELLOW}}); botLog("Warning: Extractor returned more than expected.") end
  321.             announce({{text="Extractor did not process " .. itemsLeftToProcessCount .. " item(s). Assuming unprocessable.", c=COLORS.YELLOW}})
  322.             botLog("Extractor loop: No items lost. Items in turtle: " .. validNonStackableItemsAfterSuck); break
  323.         end
  324.     end
  325.     if check_stop_signal() then return "stopped_by_user" end
  326.  
  327.     if itemsSuccessfullyProcessedFlag then
  328.         botLog("Step 8: Items processed. Waiting 15 seconds.")
  329.         for _=1,15 do if check_stop_signal() then return "stopped_by_user" end; sleep(1) end
  330.     else botLog("Step 8: No items processed. Skipping 15s wait.") end
  331.     if check_stop_signal() then return "stopped_by_user" end
  332.  
  333.     botLog("Step 9: Sucking up to 17 times from tryOutputchest (South).")
  334.     local itemsSuckedInStep9 = 0
  335.     for i = 1, 17 do
  336.         if turtle.suck() then itemsSuckedInStep9 = itemsSuckedInStep9 + 1; botLog("Final suck op "..i.." successful.")
  337.         else botLog("Final suck: Chest empty/inventory full after " .. itemsSuckedInStep9 .. " ops."); break end
  338.     end
  339.     botLog("Finished final suck from tryOutputchest.")
  340.    
  341.     botLog("Turning right (to West) after final suck.")
  342.     if not ml.turnRight() then critical_error_handler("Failed to turn right after final suck.", false); return "error" end
  343.     if check_stop_signal() then return "stopped_by_user" end
  344.  
  345.     botLog("Step 10: Final inventory check (expecting " .. expectedNonStackableCount .. " non-stackable items).")
  346.     local finalNonStackableSlotsCount = 0
  347.     for slot = 1, 16 do
  348.         turtle.select(slot)
  349.         if turtle.getItemCount(slot) > 0 then
  350.             if turtle.getItemSpace(slot) == 0 then finalNonStackableSlotsCount = finalNonStackableSlotsCount + 1
  351.             else botLog("Slot " .. slot .. " contains stackable/partial. Not counted.") end
  352.         end
  353.     end
  354.     botLog("Final count of non-stackable items/slots: " .. finalNonStackableSlotsCount)
  355.  
  356.     if finalNonStackableSlotsCount ~= expectedNonStackableCount then
  357.         critical_error_handler("Final item count (" .. finalNonStackableSlotsCount .. ") != initial (" .. expectedNonStackableCount .. ").", false) -- Error handler does not turnLeft. Original said turnRight.
  358.         ml.turnRight() -- Specific turn for this error as per prompt
  359.         return "error"
  360.     end
  361.     announce({{text="Final item count matches expected. Proceeding.", c=COLORS.GREEN}})
  362.     if check_stop_signal() then return "stopped_by_user" end
  363.  
  364.     botLog("Step 11: Turning right (to North) for dropUp.")
  365.     if not ml.turnRight() then critical_error_handler("Failed to turn right for dropUp.", false); return "error" end
  366.    
  367.     botLog("Dropping items up (16 times).")
  368.     for i = 1, 16 do
  369.         turtle.select(i)
  370.         if turtle.getItemCount(i) > 0 then
  371.             if turtle.dropUp() then botLog("Dropped up from slot " .. i)
  372.             else botLog("Failed to dropUp from slot " .. i) end
  373.         end
  374.     end
  375.     if check_stop_signal() then return "stopped_by_user" end
  376.  
  377.     botLog("== Disenchant Cycle Iteration Complete ==")
  378.     announce({{text="Disenchant cycle iteration finished.",c=COLORS.GRAY}})
  379.     return "continue_looping"
  380. end
  381.  
  382. commandHandlers.start = function(u,a)
  383.     if isDisenchantRunning then
  384.         announce({{text=CHAT_BOT_NAME.." is already running.",c=COLORS.YELLOW}})
  385.         return
  386.     end
  387.     isDisenchantRunning = true
  388.     fuelWarningSent = false
  389.     announce({{text="Starting "..CHAT_BOT_NAME.." loop...",c=COLORS.GOLD,b=true}})
  390.     botLog(u.." initiated "..CHAT_BOT_NAME.." start.")
  391.     -- The main run() loop will now pick up the actual execution
  392. end
  393.  
  394. commandHandlers.stop = function(u,a)
  395.     if not isDisenchantRunning then
  396.         announce({{text=CHAT_BOT_NAME.." is not currently running.",c=COLORS.GRAY}})
  397.         return
  398.     end
  399.     announce({{text="Stop signal sent to "..CHAT_BOT_NAME..". Will stop soon.",c=COLORS.YELLOW}})
  400.     botLog(u.." initiated "..CHAT_BOT_NAME.." stop.")
  401.     isDisenchantRunning = false
  402. end
  403. --#endregion
  404.  
  405. --#region DisenchantBot Main Loop (Event Handling)
  406. local function run()
  407.     if DEBUG_LOGGING_ENABLED then
  408.         local file, err = fs.open(LOG_FILE_NAME, "w")
  409.         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()
  410.         else print("LOGGING ERROR: Could not initialize "..LOG_FILE_NAME..": "..tostring(err)) end
  411.     end
  412.     term.clear();term.setCursorPos(1,1);
  413.  
  414.     chatBox = peripheral.find(CHAT_BOX_PERIPHERAL_NAME)
  415.     botLog(CHAT_BOT_NAME .. " initializing...")
  416.     botLog("Initial ChatBox peripheral check: " .. tostring(chatBox))
  417.     if chatBox and chatBox.sendFormattedMessage then
  418.         chatBoxFunctional = true
  419.         botLog("ChatBox determined to be FUNCTIONAL.")
  420.     else
  421.         chatBoxFunctional = false
  422.         botLog("ChatBox IS NIL or missing key methods. Marking as non-functional.")
  423.     end
  424.  
  425.     botLog(CHAT_BOT_NAME.." online. Movement Library init complete.")
  426.     print(CHAT_BOT_NAME.." online. Use '"..COMMAND_PREFIX.." help'.")
  427.  
  428.     if not announce({{text=CHAT_BOT_NAME.." online, ready for disenchanting duties!",c=COLORS.GREEN}}) then
  429.         botLog("Initial online announcement FAILED (ChatBox non-functional or error).")
  430.     end
  431.  
  432.     while true do
  433.         local event, p1, p2, p3, p4, p5
  434.  
  435.         if isDisenchantRunning then
  436.             event, p1, p2, p3, p4, p5 = os.pullEventRaw()
  437.             if not event then
  438.                 sleep(0.05) -- Small yield if no event, to prevent tight loop when running
  439.             end
  440.         else
  441.             event, p1, p2, p3, p4, p5 = os.pullEvent() -- Block if not running
  442.         end
  443.  
  444.         if event then
  445.             if event == "chat" then
  446.                 local user, message = p1, p2
  447.                 if message then
  448.                     if string.lower(message) == "@all" then
  449.                         botLog("@all from "..user)
  450.                         announce({{text="Use '",c=COLORS.GREEN},{text=COMMAND_PREFIX.." help",c=COLORS.AQUA},{text="' for my commands.",c=COLORS.GREEN}})
  451.                     elseif string.sub(message,1,#COMMAND_PREFIX) == COMMAND_PREFIX then
  452.                         botLog("Chat cmd from "..user..": "..message)
  453.                         local params={};for pt in string.gmatch(message,"[^%s]+")do table.insert(params,pt)end
  454.                         local cmdName=""; if params[2]then cmdName=string.lower(params[2])end
  455.                         local cmdArgs={};for i=3,#params do table.insert(cmdArgs,params[i])end
  456.  
  457.                         if commandHandlers[cmdName]then
  458.                             commandHandlers[cmdName](user,cmdArgs)
  459.                         elseif cmdName ~= "" then
  460.                             announce({{text="Unknown Command: '",c=COLORS.RED},{text=cmdName,c=COLORS.YELLOW},{text="'. Try '",c=COLORS.RED},{text=COMMAND_PREFIX .. " help",c=COLORS.AQUA},{text="'.",c=COLORS.RED}})
  461.                         end
  462.                     end
  463.                 end
  464.             elseif event == "terminate" then
  465.                 botLog("Terminate event received. Shutting down.")
  466.                 isDisenchantRunning = false
  467.                 if chatBoxFunctional then announce({{text=CHAT_BOT_NAME.." shutting down.",c=COLORS.YELLOW}}) end
  468.                 return
  469.             end
  470.         end
  471.  
  472.         if isDisenchantRunning then
  473.             if not isDisenchantRunning then -- Check if a command just set it to false
  474.                 announce({{text=CHAT_BOT_NAME.." loop stopping (command processed).",c=COLORS.YELLOW}})
  475.                 botLog(CHAT_BOT_NAME.." processing stopped by command processed in this iteration.")
  476.             else
  477.                 local cycleStatus = performSingleDisenchantCycle()
  478.  
  479.                 if cycleStatus == "error" then
  480.                     botLog(CHAT_BOT_NAME .." loop terminated due to error reported by cycle.")
  481.                     isDisenchantRunning = false
  482.                 elseif cycleStatus == "stopped_by_user" then
  483.                     botLog(CHAT_BOT_NAME .." loop iteration cut short by internal stop signal detection.")
  484.                     isDisenchantRunning = false
  485.                 elseif cycleStatus == "continue_looping" then
  486.                     botLog("Disenchant cycle iteration completed. Will continue if flag is set.")
  487.                 end
  488.  
  489.                 if not isDisenchantRunning then
  490.                     announce({{text=CHAT_BOT_NAME.." loop has now fully stopped.",c=COLORS.GOLD}})
  491.                     botLog(CHAT_BOT_NAME.." processing has completely stopped.")
  492.                 end
  493.             end
  494.         end
  495.     end
  496. end
  497. run()
  498. --#endregion
Add Comment
Please, Sign In to add comment