Myros27

aeBot V2

May 19th, 2025
25
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 31.46 KB | None | 0 0
  1. --[[
  2.     AEBot v2.0 - Simplified AE2 Monitor
  3.     - Focuses on AE2 item storage monitoring and basic movement.
  4.     - Integrates Movement Library from DefragBot v1.8.3.
  5.     - Alerts at 20% free storage and for each 1% drop below that.
  6.     - Uses getUsedItemStorage() and getAvailableItemStorage().
  7.     - Removed crafting, item tracking, and other advanced features.
  8. ]]
  9.  
  10. --#region Configuration
  11. local ME_BRIDGE_PERIPHERAL_NAME = "meBridge" -- IMPORTANT: Set your ME Bridge peripheral name
  12. local CHAT_BOX_PERIPHERAL_NAME = "chatBox"
  13.  
  14. local COMMAND_PREFIX = "@ae"
  15. local CHAT_BOT_NAME = "AEBot"
  16. local CHAT_BOT_BRACKETS = "[]"
  17. local CHAT_BOT_BRACKET_COLOR = "&d" -- Light Purple for AEBot
  18.  
  19. local STORAGE_CHECK_INTERVAL = 60 -- Seconds
  20. local INITIAL_STORAGE_ALERT_THRESHOLD_PERCENT = 20 -- Alert when free space is below this
  21.  
  22. local DEBUG_LOGGING_ENABLED = true
  23. local LOG_FILE_NAME = "aebot.log" -- Separate log file for AEBot
  24. --#endregion
  25.  
  26. --#region Global State
  27. local meBridge = nil
  28. local chatBox = nil
  29. local chatBoxFunctional = true
  30.  
  31. -- Storage Alert State
  32. local lastAlertedWholePercentFree = 101 -- Start high to ensure first alert triggers if below threshold
  33. local storageAlertActive = false      -- True if we've crossed the initial 20% threshold
  34.  
  35. local isAEBotLogicRunning = true -- General flag for main loop and timers
  36. local storageCheckTimerId = nil
  37. --#endregion
  38.  
  39. --#region Logger Setup
  40. 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
  41. local function botLog(msg)local fm="["..CHAT_BOT_NAME.."-Debug] "..msg; print(fm); writeToLogFile(fm)end
  42. local function moveLog(msg)local fm="[MoveLib-Debug] "..msg; print(fm); writeToLogFile(fm)end -- For the movement library
  43. --#endregion
  44.  
  45. --#region Movement Library Code (Copied from DefragBot v1.8.3)
  46. local AUTOMATA_PERIPHERAL_NAME="endAutomata";local POSITION_FILE="turtle_pos_aebot.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 for AEBot...");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};
  47. -- Note: initMoveLibrary() is called in the main run() function.
  48. --#endregion End of Integrated Movement Library
  49.  
  50. --#region AEBot Peripherals and Helpers
  51. local function sendFormattedChat(messageComponents)
  52.     local plainText = ""
  53.     if type(messageComponents) == "table" then
  54.         for _, c_comp in ipairs(messageComponents) do
  55.             plainText = plainText .. (type(c_comp) == "table" and c_comp.text or tostring(c_comp) or "")
  56.         end
  57.     elseif messageComponents ~= nil then plainText = tostring(messageComponents)
  58.     else plainText = "nil_message_components_to_sendFormattedChat" end
  59.  
  60.     if not chatBoxFunctional or not chatBox then
  61.         botLog("[NoChat] " .. plainText)
  62.         return false
  63.     end
  64.  
  65.     local jsonMessage_success, jsonMessage = pcall(textutils.serialiseJSON, messageComponents)
  66.     if not jsonMessage_success then
  67.         botLog("!!! textutils.serialiseJSON FAILED: " .. tostring(jsonMessage) .. " -- Original: " .. textutils.serialize(messageComponents,{compact=true,max_depth=2}))
  68.         print("ERROR: Could not serialize chat message!")
  69.         -- Fallback to plain text
  70.         local s,e = pcall(chatBox.sendMessage, plainText, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  71.         if not s then botLog("Plain text fallback send FAILED: " .. tostring(e)) end
  72.         return false
  73.     end
  74.  
  75.     local peripheral_call_success, peripheral_error = pcall(function()
  76.         return chatBox.sendFormattedMessage(jsonMessage, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  77.     end)
  78.  
  79.     if not peripheral_call_success then
  80.         botLog("!!! chatBox.sendFormattedMessage FAILED: " .. tostring(peripheral_error) .. " -- JSON was: " .. jsonMessage)
  81.         print("ERROR: Failed to send formatted message via chatBox: " .. tostring(peripheral_error))
  82.         -- Fallback to plain text
  83.         local s,e = pcall(chatBox.sendMessage, plainText, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  84.         if not s then botLog("Plain text fallback send FAILED: " .. tostring(e)) end
  85.         return false
  86.     end
  87.     return true
  88. end
  89.  
  90. local function announce(messageComponents)
  91.     local success = sendFormattedChat(messageComponents)
  92.     if not success then
  93.         botLog("Announce: sendFormattedChat failed or fell back to plain.")
  94.     end
  95.     sleep(0.3)
  96.     return success
  97. end
  98.  
  99. local COLORS = {GOLD="gold",AQUA="aqua",GRAY="gray",RED="red",GREEN="green",YELLOW="yellow",WHITE="white",DARK_RED="dark_red",DARK_GRAY="dark_gray", LIGHT_PURPLE="light_purple", BLUE="blue"}
  100. --#endregion
  101.  
  102. --#region AEBot Core Logic: Storage Check
  103. local function getStorageInfo()
  104.     if not meBridge then
  105.         botLog("getStorageInfo: ME Bridge not available.", true)
  106.         return nil, "ME Bridge not found or not functional."
  107.     end
  108.  
  109.     local usedBytes_s, usedBytes = pcall(meBridge.getUsedItemStorage)
  110.     local availableBytes_s, availableBytes = pcall(meBridge.getAvailableItemStorage)
  111.  
  112.     if not usedBytes_s or not availableBytes_s then
  113.         local err_msg = "Error calling ME Bridge:"
  114.         if not usedBytes_s then err_msg = err_msg .. " getUsedItemStorage failed (" .. tostring(usedBytes) .. ")." end
  115.         if not availableBytes_s then err_msg = err_msg .. " getAvailableItemStorage failed (" .. tostring(availableBytes) .. ")." end
  116.         botLog(err_msg, true)
  117.         return nil, err_msg
  118.     end
  119.    
  120.     if type(usedBytes) ~= "number" or type(availableBytes) ~= "number" then
  121.         local type_err_msg = "ME Bridge returned non-numeric storage data."
  122.         botLog(type_err_msg .. " Used: " .. tostring(usedBytes) .. ", Avail: " .. tostring(availableBytes), true)
  123.         return nil, type_err_msg
  124.     end
  125.  
  126.     local totalBytes = usedBytes + availableBytes
  127.     local freePercentage
  128.     if totalBytes > 0 then
  129.         freePercentage = availableBytes / totalBytes
  130.     else
  131.         freePercentage = 1.0 -- System empty or offline, effectively 100% free
  132.     end
  133.  
  134.     return {
  135.         used = usedBytes,
  136.         available = availableBytes,
  137.         total = totalBytes,
  138.         freePercent = freePercentage
  139.     }, nil
  140. end
  141.  
  142. local function performStorageCheck()
  143.     botLog("Performing AE storage check...")
  144.     local storageData, err = getStorageInfo()
  145.  
  146.     if not storageData then
  147.         announce({{text="AE System Error: ", color=COLORS.RED, bold=true}, {text=err or "Could not retrieve storage data.", color=COLORS.YELLOW}})
  148.         return
  149.     end
  150.  
  151.     local currentFreePercentWhole = math.floor(storageData.freePercent * 100)
  152.     local thresholdPercent = INITIAL_STORAGE_ALERT_THRESHOLD_PERCENT
  153.  
  154.     if storageData.freePercent < (thresholdPercent / 100) then
  155.         if not storageAlertActive then -- First time crossing the initial threshold
  156.             announce({{text="AE Storage CRITICAL: ", color=COLORS.DARK_RED, bold=true}, {text=currentFreePercentWhole .. "% free.", color=COLORS.RED}, {text=" (Below " .. thresholdPercent .. "% threshold!)", color=COLORS.DARK_RED}})
  157.             storageAlertActive = true
  158.             lastAlertedWholePercentFree = currentFreePercentWhole
  159.         elseif currentFreePercentWhole < lastAlertedWholePercentFree then -- Subsequent drops of at least 1 whole percent
  160.             announce({{text="AE Storage WARNING: ", color=COLORS.RED, bold=true}, {text=currentFreePercentWhole .. "% free.", color=COLORS.YELLOW}})
  161.             lastAlertedWholePercentFree = currentFreePercentWhole
  162.         else
  163.             botLog("Storage still below threshold (" .. currentFreePercentWhole .. "% free) but not a new 1% drop since last alert (" .. lastAlertedWholePercentFree .. "%).")
  164.         end
  165.     else -- Storage is >= threshold % free
  166.         if storageAlertActive then -- Was previously alerting
  167.             announce({{text="AE Storage RECOVERED: ", color=COLORS.GREEN, bold=true}, {text=currentFreePercentWhole .. "% free.", color=COLORS.AQUA}, {text=" (Now >= " .. thresholdPercent .. "%).", color=COLORS.GREEN}})
  168.             storageAlertActive = false
  169.             lastAlertedWholePercentFree = 101 -- Reset for next potential alert cycle
  170.         else
  171.             botLog("Storage OK: " .. currentFreePercentWhole .. "% free.")
  172.         end
  173.     end
  174. end
  175. --#endregion
  176.  
  177. --#region AEBot Command Handlers
  178. local commandHandlers = {}
  179.  
  180. commandHandlers.help = function(user, args)
  181.     announce({{text="--- AEBot Cmds ("..COMMAND_PREFIX..") v2.0 ---",c=COLORS.LIGHT_PURPLE,b=true}})
  182.     announce({{text=COMMAND_PREFIX.." help",c=COLORS.AQUA},{text=" - Shows this help message.",c=COLORS.GRAY}})
  183.     announce({{text=COMMAND_PREFIX.." capacity",c=COLORS.AQUA},{text=" - Shows current AE system item storage.",c=COLORS.GRAY}})
  184.     announce({{text="--- Movement Commands ---",c=COLORS.GOLD,b=true}})
  185.     announce({{text=COMMAND_PREFIX.." pos",c=COLORS.AQUA},{text=" - Current position/direction.",c=COLORS.GRAY}})
  186.     announce({{text=COMMAND_PREFIX.." fuel",c=COLORS.AQUA},{text=" - Current fuel.",c=COLORS.GRAY}})
  187.     announce({{text=COMMAND_PREFIX.." unstuck",c=COLORS.AQUA},{text=" - Warp home, reset pos.",c=COLORS.GRAY}})
  188.     announce({{text=COMMAND_PREFIX.." cmd help",c=COLORS.AQUA},{text=" - Help for raw Lua command execution.",c=COLORS.GRAY}})
  189.     announce({{text=COMMAND_PREFIX.." cmd <lua>",c=COLORS.AQUA},{text=" - Execute Lua (e.g., ml.forward()).",c=COLORS.GRAY}})
  190.     announce({{text=COMMAND_PREFIX.." mult <seq>",c=COLORS.AQUA},{text=" - Seq l,r,f,b,u,d,a.",c=COLORS.GRAY}})
  191. end
  192.  
  193. commandHandlers.capacity = function(user, args)
  194.     botLog(user .. " requested AE capacity.")
  195.     local storageData, err = getStorageInfo()
  196.  
  197.     if not storageData then
  198.         announce({{text="AE System Error: ", color=COLORS.RED, bold=true}, {text=err or "Could not retrieve storage data.", color=COLORS.YELLOW}})
  199.         return
  200.     end
  201.  
  202.     announce({{text="--- AE Item Storage Capacity ---", color=COLORS.LIGHT_PURPLE, bold=true}})
  203.     announce({{text="Used: ", color=COLORS.GRAY}, {text=string.format("%d bytes", storageData.used), color=COLORS.WHITE}})
  204.     announce({{text="Available: ", color=COLORS.GRAY}, {text=string.format("%d bytes", storageData.available), color=COLORS.WHITE}})
  205.     announce({{text="Total: ", color=COLORS.GRAY}, {text=string.format("%d bytes", storageData.total), color=COLORS.WHITE}})
  206.     announce({{text="Free: ", color=COLORS.GRAY}, {text=string.format("%.2f%%", storageData.freePercent * 100), color= (storageData.freePercent < (INITIAL_STORAGE_ALERT_THRESHOLD_PERCENT/100) and COLORS.RED or COLORS.GREEN), bold=true}})
  207. end
  208.  
  209. -- Movement Command Handlers (from DefragBot)
  210. commandHandlers.cmd = function(u,a)
  211.     if #a == 0 then announce({{text="Usage: "..COMMAND_PREFIX.." cmd <lua_code_to_run>",c=COLORS.YELLOW}}); announce({{text="Try '",c=COLORS.GRAY},{text=COMMAND_PREFIX.." cmd help",c=COLORS.AQUA},{text="' for more info.",c=COLORS.GRAY}}); return end
  212.     if #a == 1 and string.lower(a[1]) == "help" then
  213.         announce({{text="--- @ae cmd Help ---",c=COLORS.GOLD,b=true}}); announce({{text="Usage: ",c=COLORS.AQUA},{text=COMMAND_PREFIX.." cmd <lua_code_to_run>",c=COLORS.WHITE}}); announce({{text="Executes Lua. Use 'ml.' for Movement Library.",c=COLORS.GRAY}}); announce({{text="Examples:",c=COLORS.AQUA}}); announce({{text="  "..COMMAND_PREFIX.." cmd ml.moveTo(0,0,0,'N')",c=COLORS.WHITE}}); announce({{text="  "..COMMAND_PREFIX.." cmd ml.turnRight()",c=COLORS.WHITE}}); announce({{text="  "..COMMAND_PREFIX.." cmd return ml.getFuelLevel()",c=COLORS.WHITE}}); announce({{text="Note: Lua code is case-sensitive (e.g., 'ml.moveTo').",c=COLORS.YELLOW}}); return
  214.     end
  215.     local cs=table.concat(a," "); botLog("User "..u.." executing raw command: "..cs)
  216.     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})
  217.     if not fn then announce({{text="Error parsing command: ",c=COLORS.RED},{text=tostring(e),c=COLORS.YELLOW}}); botLog("Parse error for raw command '"..cs.."': "..tostring(e)); return end
  218.     local s,r=pcall(fn)
  219.     if s then announce({{text="Cmd executed. Result: ",c=COLORS.GREEN},{text=tostring(r),c=COLORS.WHITE}}); botLog("Raw command '"..cs.."' result: "..tostring(r))
  220.     else announce({{text="Error executing command: ",c=COLORS.RED},{text=tostring(r),c=COLORS.YELLOW}}); botLog("Execution error for raw command '"..cs.."': "..tostring(r)) end
  221. end
  222. commandHandlers.mult = function(u,a)
  223.     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
  224.     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
  225.     for i=1,#sq do
  226.         local mc=string.sub(sq,i,i);local mf=nil
  227.         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
  228.         elseif mc=="u"then mf=ml.u elseif mc=="d"then mf=ml.d elseif mc=="a"then mf=ml.a end
  229.         if mf then
  230.             announce({{text="Exec: ",c=COLORS.GRAY},{text=mc,c=COLORS.YELLOW}})
  231.             local ms=mf()
  232.             if not ms then announce({{text="Move '",c=COLORS.RED},{text=mc,c=COLORS.YELLOW},{text="' failed. Stopping sequence.",c=COLORS.RED}}); if turtle.getFuelLevel()<10 and turtle.getFuelLimit()~=0 then announce({{text="CRITICAL: Fuel low ("..turtle.getFuelLevel()..")!",c=COLORS.DARK_RED,b=true}}) end; sc=false;break end
  233.         else announce({{text="Unknown char '",c=COLORS.RED},{text=mc,c=COLORS.YELLOW},{text="' in sequence. Stopping.",c=COLORS.RED}});sc=false;break end
  234.     end
  235.     if sc then announce({{text="Sequence finished.",c=COLORS.GREEN}})end
  236. end
  237. commandHandlers.pos = function(u,_) local pD=ml.getPosition();local dNS=ml.getDirectionName(); 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}}) else announce({{text="Position unknown.",c=COLORS.YELLOW}})end end
  238. commandHandlers.fuel = function(u,_) local l,lm=turtle.getFuelLevel(),turtle.getFuelLimit(); announce({{text="Fuel: ",c=COLORS.AQUA},{text=tostring(l)..(lm>0 and(" / "..tostring(lm))or" (Unlimited)"),c=COLORS.WHITE}}) end
  239. commandHandlers.unstuck = function(u,_) announce({{text="Unstuck: Attempting to warp home and reset position...",c=COLORS.YELLOW}});botLog(u.." used unstuck command."); if ml.h()then announce({{text="Warped home and position reset successfully.",c=COLORS.GREEN}}) else announce({{text="Failed to warp home.",c=COLORS.RED}})end end
  240. --#endregion
  241.  
  242. --#region AEBot Main Loop
  243. local function run()
  244.     if DEBUG_LOGGING_ENABLED then
  245.         local file, err = fs.open(LOG_FILE_NAME, "w")
  246.         if file then file.write(string.format("[%s] %s Log Initialized (v2.0).\n", os.date("%Y-%m-%d %H:%M:%S"), CHAT_BOT_NAME)); file.write("============================================================\n"); file.close()
  247.         else print("LOGGING ERROR: Could not initialize "..LOG_FILE_NAME..": "..tostring(err)) end
  248.     end
  249.     term.clear();term.setCursorPos(1,1)
  250.  
  251.     -- Initialize Peripherals
  252.     meBridge = peripheral.find(ME_BRIDGE_PERIPHERAL_NAME)
  253.     chatBox = peripheral.find(CHAT_BOX_PERIPHERAL_NAME)
  254.  
  255.     botLog(CHAT_BOT_NAME .. " initializing...")
  256.     botLog("ME Bridge peripheral check: " .. tostring(meBridge))
  257.     botLog("ChatBox peripheral check: " .. tostring(chatBox))
  258.  
  259.     if not meBridge then
  260.         print("FATAL: ME Bridge ('"..ME_BRIDGE_PERIPHERAL_NAME.."') not found! AEBot cannot function.")
  261.         botLog("FATAL: ME Bridge not found. Script will not run main logic.", true)
  262.         isAEBotLogicRunning = false -- Prevent timer starts etc.
  263.     end
  264.  
  265.     if chatBox and chatBox.sendFormattedMessage then
  266.         chatBoxFunctional = true
  267.         botLog("ChatBox determined to be FUNCTIONAL.")
  268.     else
  269.         chatBoxFunctional = false
  270.         botLog("ChatBox peripheral IS NIL or lacks sendFormattedMessage. Chat will be console/log only.", true)
  271.         if chatBox then print("WARNING: ChatBox found but missing key methods.") else print("WARNING: ChatBox not found.") end
  272.     end
  273.  
  274.     -- Initialize Movement Library
  275.     ml.init() -- Call the init function from the movement library
  276.     botLog(CHAT_BOT_NAME.." online. MoveLib init complete.")
  277.     print(CHAT_BOT_NAME.." online. '"..COMMAND_PREFIX.." help' or '@all'.")
  278.  
  279.     local initial_announce_success = announce({{text=CHAT_BOT_NAME.." online (v2.0)! Monitoring AE System.",c=COLORS.GREEN, b=true}})
  280.     if not initial_announce_success then
  281.         botLog("Initial online announcement FAILED.")
  282.     end
  283.  
  284.     if isAEBotLogicRunning then
  285.         performStorageCheck() -- Initial check
  286.         storageCheckTimerId = os.startTimer(STORAGE_CHECK_INTERVAL)
  287.         botLog("Initial storage check complete. Timer ID: " .. tostring(storageCheckTimerId))
  288.     end
  289.  
  290.     while true do
  291.         local eventData={os.pullEvent()}
  292.         local eventType=eventData[1]
  293.  
  294.         if eventType=="chat"then
  295.             local eU,eM=eventData[2],eventData[3]
  296.             if eM then
  297.                 if string.lower(eM)=="@all"then
  298.                     botLog("@all from "..eU)
  299.                     announce({{text=CHAT_BOT_NAME,c=COLORS.LIGHT_PURPLE,b=true},{text=": Use '",c=COLORS.GREEN},{text=COMMAND_PREFIX.." help",c=COLORS.AQUA},{text="' for my commands.",c=COLORS.GREEN}})
  300.                 elseif string.sub(eM,1,#COMMAND_PREFIX)==COMMAND_PREFIX then
  301.                     botLog("Chat cmd from "..eU..": "..eM)
  302.                     local p={};for pt in string.gmatch(eM,"[^%s]+")do table.insert(p,pt)end
  303.                     local cn=""; if p[2]then cn=string.lower(p[2])end
  304.                     local ca={};for i=3,#p do table.insert(ca,p[i])end
  305.  
  306.                     if commandHandlers[cn]then
  307.                         commandHandlers[cn](eU,ca)
  308.                     elseif cn~=""then
  309.                         if p[2] and string.find(p[2], "%(") and string.find(p[2], "%)") then
  310.                             announce({{text="Unknown Command: '",c=COLORS.RED},{text=p[2],c=COLORS.YELLOW},{text="'. Try: '",c=COLORS.RED},{text=COMMAND_PREFIX.." cmd "..p[2],c=COLORS.AQUA},{text="'",c=COLORS.RED}})
  311.                         else
  312.                             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}})
  313.                         end
  314.                     end
  315.                 end
  316.             end
  317.         elseif eventType=="timer"then
  318.             local tID = eventData[2]
  319.             if isAEBotLogicRunning and tID == storageCheckTimerId then
  320.                 botLog("Storage check timer fired.")
  321.                 performStorageCheck()
  322.                 storageCheckTimerId = os.startTimer(STORAGE_CHECK_INTERVAL) -- Restart timer
  323.                 botLog("Storage check timer restarted. New ID: " .. tostring(storageCheckTimerId))
  324.             else
  325.                 botLog("Unknown or non-AEBot timer event: " .. tostring(tID))
  326.             end
  327.         elseif eventType=="terminate"then
  328.             botLog("Terminate event received. Shutting down " .. CHAT_BOT_NAME .. ".", true)
  329.             isAEBotLogicRunning = false -- Stop any further timed actions
  330.             if storageCheckTimerId then os.cancelTimer(storageCheckTimerId); botLog("Storage check timer cancelled.") end
  331.             if chatBoxFunctional then announce({{text=CHAT_BOT_NAME.." shutting down.",c=COLORS.YELLOW, b=true}}) end
  332.             return
  333.         else
  334.             botLog("Unhandled event: " .. eventType .. " - Data: " .. textutils.serialize(eventData))
  335.         end
  336.     end
  337. end
  338.  
  339. run()
  340. --#endregion
Add Comment
Please, Sign In to add comment