AgentPothead

AE2ColonyWarehouse.lua

Feb 9th, 2026
43
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 26.50 KB | None | 0 0
  1. ------------------------------------------------------------
  2. -- AE2ColonyWarehouse (Tile-Based)
  3. -- by Dww160 (based on Zucanthor logic)
  4. --
  5. -- Recommended Monitor Setup:
  6. -- For best results, use a 3x5 advanced monitor (3 tall x 5 wide).
  7. --
  8. -- Features:
  9. -- 1) Scans MineColonies requests
  10. -- 2) Checks AE2 (via meBridge) for items
  11. -- 3) Exports items to any connected inventory on the ME Bridge
  12. -- 4) Attempts to craft if not enough items are present
  13. -- 5) Displays requests as interactive color-coded "tiles"
  14. -- 6) Click a tile to see detail view (colonist name, skip reason, craftability, etc.)
  15. -- 7) In the detail view, tap the toggle option to add or remove the request from skip patterns.
  16. -- 8) In detail view, requests that fail are labeled as "none in inventory".
  17. -- 9) Auto-returns to tile menu after 30s or on a second click.
  18. -- 10) Runs every 30 seconds by default, with a visible scan timer.
  19. -- 11) When there are no requests to fulfill, displays an animated happy stick figure dance with the message "All requests filled! Hurray!"
  20. --
  21. -- Original Tutorial Lines:
  22. -- This script is based on the original tutorial code for AE2ColonyWarehouse.
  23. ------------------------------------------------------------
  24.  
  25. --------------------
  26. -- USER SETTINGS
  27. --------------------
  28. local SCAN_INTERVAL = 10 -- seconds between scans
  29. local LOG_FILE = "ae2colonywarehouse.log"
  30. local DEBUG = true -- set to false to reduce console spam
  31. local DETAIL_TIMEOUT = 30 -- seconds to show detail view
  32.  
  33. -- Crafting timeout in seconds (if no progress within this time, job is marked timed out)
  34. local CRAFT_TIMEOUT = 90
  35. local MAX_ATTEMPTS = 3 -- Maximum re-schedule attempts per item
  36.  
  37. -- If you want to blacklist certain item requests or skip them,
  38. -- add them here.
  39. local SKIP_PATTERNS = {
  40. "Tool of class",
  41. "Hoe", "Shovel", "Axe", "Pickaxe", "Bow", "Sword", "Shield",
  42. "Helmet", "Chestplate", "Leggings", "Boots", "Tunic", "Leather Cap",
  43. "Rallying Banner", "Crafter", "Compostable", "Fertilizer",
  44. "Flowers", "Food", "Fuel", "Smeltable Ore", "Stack List"
  45. }
  46.  
  47. --------------------
  48. -- GLOBAL CRAFTING TRACKER
  49. --------------------
  50. -- Stores pending craft jobs:
  51. -- craftingTracker[itemName] = { expected = number, timestamp = os.clock(), attempts = number, timedOut = boolean }
  52. local craftingTracker = {}
  53.  
  54. --------------------
  55. -- HELPER FUNCTIONS
  56. --------------------
  57. local function dprint(...)
  58. if DEBUG then print("[DEBUG]", ...) end
  59. end
  60.  
  61. local function safePeripheralCall(periph, method, ...)
  62. if type(periph[method]) ~= "function" then
  63. dprint("Method " .. method .. " not available on peripheral")
  64. return nil, "Method " .. method .. " not available"
  65. end
  66. local ok, result = pcall(periph[method], periph, ...)
  67. if not ok then
  68. dprint("Error calling " .. method .. ": " .. tostring(result))
  69. return nil, result
  70. end
  71. return result, nil
  72. end
  73.  
  74. --------------------
  75. -- PERIPHERAL SETUP
  76. --------------------
  77. -- Now that all helper functions are defined, set up peripherals.
  78. local monitor = peripheral.find("monitor")
  79. if not monitor then error("No advanced monitor found. Attach an advanced monitor to the computer.") end
  80. monitor.setTextScale(0.5)
  81. monitor.setBackgroundColor(colors.black)
  82. monitor.setTextColor(colors.white)
  83. monitor.clear()
  84. monitor.setCursorPos(1, 1)
  85. monitor.setCursorBlink(false)
  86.  
  87. local meBridge = peripheral.find("me_bridge")
  88. if not meBridge then error("No 'meBridge' found. Place an ME Bridge next to the computer.") end
  89.  
  90. local colony = peripheral.find("colony_integrator")
  91. if not colony then error("No 'colonyIntegrator' found. Place a Colony Integrator next to the computer.") end
  92. if not colony.isInColony then error("Colony Integrator is not inside a colony. Please move it within your colony's boundaries.") end
  93.  
  94. ----------------------------------------------------------------
  95. -- autoDetectOutputSide: Try common sides until one returns a positive export.
  96. ----------------------------------------------------------------
  97. local function autoDetectOutputSide(itemStack)
  98. local sides = {"top", "bottom", "left", "right", "front", "back"}
  99. for _, side in ipairs(sides) do
  100. local ok, result = pcall(function()
  101. return meBridge.exportItem(itemStack, side)
  102. end)
  103. if ok and type(result) == "number" and result > 0 then
  104. return side, result
  105. end
  106. end
  107. return nil, 0
  108. end
  109.  
  110. ----------------------------------------------------------------
  111. -- safeExportItemToTop: new signature => exportItem(itemStack, side, [count?])
  112. ----------------------------------------------------------------
  113. local function safeExportItemToTop(stack)
  114. local itemStack = { name = stack.name, count = stack.count or 1 }
  115. local side, result = autoDetectOutputSide(itemStack)
  116. if side then
  117. dprint("Exporting " .. itemStack.count .. " of " .. itemStack.name .. " to side: " .. side)
  118. return result
  119. else
  120. dprint("Failed to export item (" .. (stack.name or "unknown") .. "): no output inventory found")
  121. return 0
  122. end
  123. end
  124.  
  125. --------------------
  126. -- Updated safeCraftItem Function
  127. --------------------
  128. local function safeCraftItem(stack)
  129. local itemStack = { name = stack.name, count = stack.count or 1 }
  130.  
  131. -- (Optional) Confirm it’s truly craftable
  132. local isReallyCraftable = false
  133. local craftableItems = meBridge.listCraftableItems() or {}
  134. for _, c in ipairs(craftableItems) do
  135. if c.name == stack.name then
  136. isReallyCraftable = true
  137. break
  138. end
  139. end
  140. if not isReallyCraftable then
  141. dprint("No valid pattern for " .. (stack.name or "unknown") .. " in AE2. Aborting craft.")
  142. return false
  143. end
  144.  
  145. local ok, result = pcall(meBridge.craftItem, itemStack)
  146. if not ok then
  147. dprint("Error calling craftItem for " .. (stack.name or "unknown") .. ": " .. tostring(result))
  148. return false
  149. end
  150.  
  151. dprint("craftItem raw result for " .. (stack.name or "unknown") .. ": " .. textutils.serialize(result))
  152.  
  153. -- If result is a table with .status = "SUCCESS", good. If it's always 'true', might be a mod quirk.
  154. if type(result) == "table" then
  155. if result.status == "SUCCESS" then
  156. return true
  157. else
  158. dprint("craftItem returned status=" .. tostring(result.status) .. " for " .. (stack.name or "unknown"))
  159. return false
  160. end
  161. elseif result == true then
  162. -- Possibly always returns true. Let's do a quick check of meBridge.listCraftingJobs() if it exists:
  163. if type(meBridge.listCraftingJobs) == "function" then
  164. local jobs = meBridge.listCraftingJobs()
  165. local found = false
  166. for _, job in ipairs(jobs or {}) do
  167. if job.name == stack.name then
  168. found = true
  169. break
  170. end
  171. end
  172. if not found then
  173. dprint("WARNING: craftItem returned 'true' but job not found in listCraftingJobs()")
  174. return false
  175. end
  176. end
  177. return true
  178. end
  179.  
  180. return false
  181. end
  182.  
  183. local function shouldSkip(desc, name)
  184. for _, pattern in ipairs(SKIP_PATTERNS) do
  185. if string.find(desc, pattern) or string.find(name, pattern) then
  186. return true, pattern
  187. end
  188. end
  189. return false, nil
  190. end
  191.  
  192. local function toggleSkipOption(req)
  193. if req.skip then
  194. local pattern = string.match(req.skipReason or "", 'Matched skip pattern: "(.+)"')
  195. if not pattern then pattern = req.name end
  196. for i, p in ipairs(SKIP_PATTERNS) do
  197. if p == pattern then
  198. table.remove(SKIP_PATTERNS, i)
  199. dprint("Removed skip pattern: " .. pattern)
  200. break
  201. end
  202. end
  203. else
  204. table.insert(SKIP_PATTERNS, req.name)
  205. dprint("Added skip pattern: " .. req.name)
  206. end
  207. end
  208.  
  209. local function drawRect(mon, x1, y1, x2, y2, color)
  210. local oldBG, oldFG = mon.getBackgroundColor(), mon.getTextColor()
  211. mon.setBackgroundColor(color)
  212. for x = x1, x2 do
  213. mon.setCursorPos(x, y1) mon.write(" ")
  214. mon.setCursorPos(x, y2) mon.write(" ")
  215. end
  216. for y = y1, y2 do
  217. mon.setCursorPos(x1, y) mon.write(" ")
  218. mon.setCursorPos(x2, y) mon.write(" ")
  219. end
  220. mon.setBackgroundColor(colors.black)
  221. for yy = y1+1, y2-1 do
  222. mon.setCursorPos(x1+1, yy)
  223. mon.write(string.rep(" ", x2 - x1 - 1))
  224. end
  225. mon.setBackgroundColor(oldBG)
  226. mon.setTextColor(oldFG)
  227. end
  228.  
  229. local function writeInTile(mon, x, y, text, fg)
  230. local oldBG, oldFG = mon.getBackgroundColor(), mon.getTextColor()
  231. if fg then mon.setTextColor(fg) end
  232. mon.setCursorPos(x, y)
  233. mon.write(text)
  234. mon.setBackgroundColor(oldBG)
  235. mon.setTextColor(oldFG)
  236. end
  237.  
  238. local function colorToShortStatus(color)
  239. if color == colors.green then return "Fulfilled"
  240. elseif color == colors.yellow then return "Crafting"
  241. elseif color == colors.red then return "Failed"
  242. elseif color == colors.blue then return "Skipped" end
  243. return "Unknown"
  244. end
  245.  
  246. local function colorToLongStatus(color)
  247. if color == colors.green then return "Fulfilled"
  248. elseif color == colors.yellow then return "Crafting/Partial"
  249. elseif color == colors.red then return "None in inventory"
  250. elseif color == colors.blue then return "Skipped/Manual" end
  251. return "Unknown"
  252. end
  253.  
  254. -- Stub functions for meBridge (override if provided)
  255. function meBridge.isItemCrafting(stack)
  256. return false -- We rely on our own craftingTracker
  257. end
  258.  
  259. function meBridge.exportItemToTop(stack)
  260. return safeExportItemToTop(stack)
  261. end
  262.  
  263. if type(meBridge.craftItem) ~= "function" then
  264. function meBridge.craftItem(itemStack)
  265. dprint("craftItem not implemented on meBridge")
  266. return false
  267. end
  268. end
  269.  
  270. --------------------
  271. -- ANIMATED HAPPY DANCE (When No Requests)
  272. --------------------
  273. local function displayHappyDance()
  274. local w, h = monitor.getSize()
  275. for y = 4, h do
  276. monitor.setCursorPos(1, y)
  277. monitor.clearLine()
  278. end
  279. local danceFrames = {
  280. " O \n /|\\ \n / \\ ",
  281. " O \n \\|/ \n / \\ ",
  282. " O \n /|\\ \n / \\ ",
  283. " O \n \\|/ \n / \\ "
  284. }
  285. local danceMsg = "All requests filled! Hurray!"
  286. local duration = 5
  287. local startTime = os.clock()
  288. while os.clock() - startTime < duration do
  289. for _, frame in ipairs(danceFrames) do
  290. for y = 4, h do
  291. monitor.setCursorPos(1, y)
  292. monitor.clearLine()
  293. end
  294. local lines = {}
  295. for line in frame:gmatch("[^\n]+") do
  296. table.insert(lines, line)
  297. end
  298. local totalLines = #lines
  299. local startRow = math.floor((h - totalLines) / 2)
  300. for i, text in ipairs(lines) do
  301. local x = math.floor((w - #text) / 2)
  302. if x < 1 then x = 1 end
  303. monitor.setCursorPos(x, startRow + i)
  304. monitor.write(text)
  305. end
  306. local msgX = math.floor((w - #danceMsg) / 2)
  307. monitor.setCursorPos(msgX, startRow + totalLines + 1)
  308. monitor.setTextColor(colors.green)
  309. monitor.write(danceMsg)
  310. monitor.setTextColor(colors.white)
  311. sleep(0.5)
  312. end
  313. end
  314. for y = 4, h do
  315. monitor.setCursorPos(1, y)
  316. monitor.clearLine()
  317. end
  318. end
  319.  
  320. --------------------
  321. -- DISPLAY BANNER & LEGEND (Original Tutorial Lines Included)
  322. --------------------
  323. local function drawBanner()
  324. local w, _ = monitor.getSize()
  325. monitor.setCursorPos(1, 1)
  326. monitor.setBackgroundColor(colors.gray)
  327. monitor.setTextColor(colors.white)
  328. local text = "MineColonies AE2 Warehouse Manager"
  329. local x = math.floor((w - #text) / 2)
  330. if x < 1 then x = 1 end
  331. monitor.setCursorPos(x, 1)
  332. monitor.write(text)
  333. monitor.setBackgroundColor(colors.black)
  334. monitor.setTextColor(colors.white)
  335. end
  336.  
  337. local function displayLegend()
  338. local legendText = "Legend: Green=Fulfilled | Yellow=Crafting | Red=None in inventory | Blue=Skipped"
  339. local w, _ = monitor.getSize()
  340. monitor.setCursorPos(1, 2)
  341. monitor.clearLine()
  342. local x = math.floor((w - #legendText) / 2)
  343. if x < 1 then x = 1 end
  344. monitor.setCursorPos(x, 2)
  345. monitor.write(legendText)
  346. end
  347.  
  348. --------------------
  349. -- SCAN & FULFILL LOGIC
  350. --------------------
  351. local function logRequests(requests)
  352. local f = fs.open(LOG_FILE, "w")
  353. if not f then
  354. dprint("Failed to open log file: " .. LOG_FILE)
  355. return
  356. end
  357. local success, json = pcall(textutils.serializeJSON, requests, true)
  358. if success then
  359. f.write(json)
  360. else
  361. f.write("Could not JSON-serialize requests: " .. tostring(json))
  362. end
  363. f.close()
  364. end
  365.  
  366. --------------------
  367. -- Revised processRequests Function
  368. --------------------
  369. local function processRequests()
  370. local allRequests = colony.getRequests()
  371. if not allRequests then
  372. dprint("No requests returned from colony.")
  373. return {}, {}, {}
  374. end
  375. logRequests(allRequests)
  376. local equipment_list = {}
  377. local builder_list = {}
  378. local nonbuilder_list = {}
  379.  
  380. -- Build current AE2 item counts.
  381. local aeItems = {}
  382. local allAE = meBridge.listItems()
  383. for _, data in ipairs(allAE or {}) do
  384. if data.name then
  385. aeItems[data.name] = (aeItems[data.name] or 0) + (data.count or 0)
  386. end
  387. end
  388.  
  389. -- Optional: Log current crafting jobs if available.
  390. if type(meBridge.listCraftingJobs) == "function" then
  391. local jobs = meBridge.listCraftingJobs()
  392. dprint("Current crafting jobs: " .. textutils.serialize(jobs))
  393. end
  394.  
  395. -- Update craftingTracker: mark crafts as timed out if no progress.
  396. for item, info in pairs(craftingTracker) do
  397. local current = aeItems[item] or 0
  398. if current >= info.expected then
  399. dprint("Craft complete for " .. item)
  400. craftingTracker[item] = nil
  401. elseif os.clock() - info.timestamp > CRAFT_TIMEOUT then
  402. dprint("Crafting timed out for " .. item)
  403. info.timedOut = true
  404. elseif current <= (info.lastCount or 0) then
  405. dprint("No progress on " .. item .. ", forcing re-schedule")
  406. info.timedOut = true
  407. else
  408. info.lastCount = current
  409. end
  410. end
  411.  
  412. -- Build craftable set.
  413. local craftableSet = {}
  414. local craftableItems = meBridge.listCraftableItems() or {}
  415. for _, citem in ipairs(craftableItems) do
  416. craftableSet[citem.name] = true
  417. end
  418.  
  419. --------------------
  420. -- Pre-Aggregate Requests for Crafting
  421. --------------------
  422. local neededTotals = {}
  423. for _, req in ipairs(allRequests) do
  424. local item = req.items[1].name
  425. neededTotals[item] = (neededTotals[item] or 0) + req.count
  426. end
  427.  
  428. for item, totalNeeded in pairs(neededTotals) do
  429. local available = aeItems[item] or 0
  430. if available < totalNeeded then
  431. local missing = totalNeeded - available
  432. if craftableSet[item] then
  433. if not craftingTracker[item] then
  434. local success = safeCraftItem({ name = item, count = missing })
  435. if success then
  436. craftingTracker[item] = {
  437. expected = missing,
  438. timestamp = os.clock(),
  439. attempts = 1,
  440. timedOut = false,
  441. lastCount = available
  442. }
  443. dprint("[Pre-aggregated scheduled craft] " .. missing .. "x " .. item)
  444. else
  445. dprint("[Pre-aggregated craft failed] " .. item)
  446. end
  447. else
  448. if not craftingTracker[item].timedOut then
  449. local success = safeCraftItem({ name = item, count = missing })
  450. if success then
  451. craftingTracker[item].expected = craftingTracker[item].expected + missing
  452. dprint("[Pre-aggregated re-triggered craft] Updated expected count for " .. item .. " by " .. missing)
  453. end
  454. end
  455. end
  456. else
  457. dprint("[Not craftable in pre-aggregation] " .. item)
  458. end
  459. end
  460. end
  461.  
  462. --------------------
  463. -- Process Individual Requests for Display
  464. --------------------
  465. for _, req in pairs(allRequests) do
  466. local reqName = req.name
  467. local reqItem = req.items[1].name
  468. local reqTarget = req.target
  469. local reqDesc = req.desc
  470. local reqNeeded = req.count
  471. local provided = 0
  472. local skip, matchedPattern = shouldSkip(reqDesc, reqName)
  473. local color = colors.blue
  474. local haveCount = aeItems[reqItem] or 0
  475. local skipReason = nil
  476.  
  477. if skip then
  478. skipReason = "Matched skip pattern: \"" .. matchedPattern .. "\""
  479. dprint("[Skipped request]", reqName, skipReason)
  480. else
  481. if haveCount > 0 then
  482. local toExport = math.min(haveCount, reqNeeded)
  483. dprint(string.format("Exporting up to %d/%d of %s", toExport, reqNeeded, reqItem))
  484. local actuallyExported = meBridge.exportItemToTop({ name = reqItem, count = toExport })
  485. provided = provided + actuallyExported
  486. end
  487.  
  488. if provided < reqNeeded then
  489. local missing = reqNeeded - provided
  490. if not craftableSet[reqItem] then
  491. color = colors.red
  492. dprint("[Not craftable] " .. reqItem)
  493. elseif craftingTracker[reqItem] then
  494. if craftingTracker[reqItem].timedOut then
  495. if craftingTracker[reqItem].attempts < MAX_ATTEMPTS then
  496. local success = safeCraftItem({ name = reqItem, count = missing })
  497. if success then
  498. color = colors.yellow
  499. dprint("[Re-scheduled craft] " .. reqItem .. " attempt " ..
  500. (craftingTracker[reqItem].attempts + 1) .. " for additional " .. missing)
  501. craftingTracker[reqItem].attempts = craftingTracker[reqItem].attempts + 1
  502. craftingTracker[reqItem].timestamp = os.clock()
  503. craftingTracker[reqItem].timedOut = false
  504. craftingTracker[reqItem].expected = craftingTracker[reqItem].expected + missing
  505. craftingTracker[reqItem].lastCount = aeItems[reqItem] or 0
  506. else
  507. color = colors.red
  508. dprint("[Failed to re-schedule craft] " .. reqItem)
  509. end
  510. else
  511. color = colors.red
  512. dprint("[Max attempts reached for] " .. reqItem)
  513. end
  514. else
  515. -- Craft is already in progress; update expected count (pre-aggregation already called safeCraftItem).
  516. craftingTracker[reqItem].expected = craftingTracker[reqItem].expected + missing
  517. color = colors.yellow
  518. dprint("[Aggregated craft] Updated expected count for " .. reqItem ..
  519. " by " .. missing .. " to " .. craftingTracker[reqItem].expected)
  520. end
  521. else
  522. local success = safeCraftItem({ name = reqItem, count = missing })
  523. if success then
  524. color = colors.yellow
  525. dprint("[Scheduled craft] " .. missing .. "x " .. reqItem)
  526. craftingTracker[reqItem] = {
  527. expected = missing,
  528. timestamp = os.clock(),
  529. attempts = 1,
  530. timedOut = false,
  531. lastCount = aeItems[reqItem] or 0
  532. }
  533. else
  534. color = colors.red
  535. dprint("[Failed to craft] " .. reqItem)
  536. end
  537. end
  538. end
  539.  
  540. if provided >= reqNeeded then
  541. color = colors.green
  542. dprint("[Provided]", reqItem)
  543. end
  544. end
  545.  
  546. local newItem = {
  547. name = reqName,
  548. item = reqItem,
  549. target = reqTarget,
  550. desc = reqDesc,
  551. needed = reqNeeded,
  552. provided = provided,
  553. color = color,
  554. craftable = (craftableSet[reqItem] == true),
  555. skip = skip,
  556. skipReason = skipReason,
  557. }
  558. if string.find(reqDesc, "of class") then
  559. table.insert(equipment_list, newItem)
  560. elseif string.find(reqTarget, "Builder") then
  561. table.insert(builder_list, newItem)
  562. else
  563. table.insert(nonbuilder_list, newItem)
  564. end
  565. end
  566. return equipment_list, builder_list, nonbuilder_list
  567. end
  568.  
  569. --------------------
  570. -- TILE-BASED MENU
  571. --------------------
  572. local tilePositions = {}
  573.  
  574. local function flattenData(equipment, builder, nonbuilder)
  575. local merged = {}
  576. for _, v in ipairs(equipment) do table.insert(merged, v) end
  577. for _, v in ipairs(builder) do table.insert(merged, v) end
  578. for _, v in ipairs(nonbuilder) do table.insert(merged, v) end
  579. return merged
  580. end
  581.  
  582. local function displayTileMenu(requests)
  583. tilePositions = {}
  584. local w, h = monitor.getSize()
  585. local startY = 4
  586. local columns = 3
  587. local rows = 3
  588. local tileCount = columns * rows
  589. local total = #requests
  590. local leftover = total - tileCount
  591. if leftover < 0 then leftover = 0 end
  592. local tileWidth = math.floor(w / columns)
  593. local tileHeight = 6
  594. for yy = 4, h do
  595. monitor.setCursorPos(1, yy)
  596. monitor.clearLine()
  597. end
  598. local idx = 1
  599. for r = 1, rows do
  600. for c = 1, columns do
  601. if idx > total or idx > tileCount then break end
  602. local req = requests[idx]
  603. local x1 = (c - 1) * tileWidth + 1
  604. local y1 = (r - 1) * tileHeight + startY
  605. local x2 = x1 + tileWidth - 1
  606. local y2 = y1 + tileHeight - 1
  607. if x2 > w then x2 = w end
  608. if y2 > h then y2 = h end
  609. drawRect(monitor, x1, y1, x2, y2, req.color)
  610. local lineY = y1 + 1
  611. local shortStatus = colorToShortStatus(req.color)
  612. writeInTile(monitor, x1 + 2, lineY, shortStatus, req.color)
  613. lineY = lineY + 1
  614. local displayName = ""
  615. if req.name:match("^[0-9]") then
  616. displayName = string.format("%d/%s", req.provided, req.name)
  617. else
  618. displayName = string.format("%d %s", req.needed, req.name)
  619. end
  620. writeInTile(monitor, x1 + 2, lineY, displayName, colors.white)
  621. table.insert(tilePositions, {
  622. x1 = x1, y1 = y1,
  623. x2 = x2, y2 = y2,
  624. requestRef = req
  625. })
  626. idx = idx + 1
  627. end
  628. if idx > tileCount then break end
  629. end
  630. if leftover > 0 then
  631. monitor.setCursorPos(1, h)
  632. monitor.clearLine()
  633. monitor.setCursorPos(1, h)
  634. monitor.setTextColor(colors.gray)
  635. monitor.write(string.format("(%d more requests not displayed)", leftover))
  636. monitor.setTextColor(colors.white)
  637. end
  638. end
  639.  
  640. --------------------
  641. -- DETAIL VIEW
  642. --------------------
  643. local detailMode = false
  644. local detailTimeLeft = 0
  645. local detailRequest = nil
  646. local detailToggleButton = nil
  647.  
  648. local function displayDetailScreen(req)
  649. detailMode = true
  650. detailRequest = req
  651. local w, h = monitor.getSize()
  652. for y = 3, h do
  653. monitor.setCursorPos(1, y)
  654. monitor.clearLine()
  655. end
  656. local row = 4
  657. local function line(text, col)
  658. monitor.setCursorPos(2, row)
  659. monitor.setTextColor(col or colors.white)
  660. monitor.clearLine()
  661. monitor.write(text)
  662. row = row + 1
  663. end
  664. line("Item: " .. req.name, req.color)
  665. line(string.format("Needed: %d / Provided: %d", req.needed, req.provided), req.color)
  666. line("Requested By: " .. req.target, req.color)
  667. row = row + 1
  668. local statusStr = (req.color == colors.red) and "None in inventory" or colorToLongStatus(req.color)
  669. line("Status: " .. statusStr, req.color)
  670. line("Craftable by AE2: " .. (req.craftable and "Yes" or "No"), req.color)
  671. row = row + 1
  672. if req.skip then
  673. line("Skip Reason: " .. (req.skipReason or "No reason found."), colors.blue)
  674. line("Suggestion: Remove from skip patterns", colors.lightBlue)
  675. else
  676. line("Skip Option: Not skipped", colors.lightBlue)
  677. end
  678. row = row + 1
  679. local toggleText = req.skip and "Tap to REMOVE skip pattern" or "Tap to ADD skip pattern"
  680. line(toggleText, colors.cyan)
  681. detailToggleButton = { x1 = 1, y = row - 1, x2 = w }
  682. monitor.setCursorPos(1, h)
  683. monitor.clearLine()
  684. local msg = string.format("(Detail view closes in %ds)", DETAIL_TIMEOUT)
  685. local x = math.floor((w - #msg) / 2)
  686. if x < 1 then x = 1 end
  687. monitor.setCursorPos(x, h)
  688. monitor.setTextColor(colors.gray)
  689. monitor.write(msg)
  690. end
  691.  
  692. --------------------
  693. -- TIMERS & MAIN LOOP
  694. --------------------
  695. local scanTimeLeft = SCAN_INTERVAL
  696. local equipmentData, builderData, nonbuilderData
  697.  
  698. local function updateCountdownLine()
  699. local w, _ = monitor.getSize()
  700. monitor.setCursorPos(1, 3)
  701. monitor.clearLine()
  702. if detailMode then
  703. local msg = string.format("Detail closes in %ds", detailTimeLeft)
  704. local x = math.floor((w - #msg) / 2)
  705. if x < 1 then x = 1 end
  706. monitor.setCursorPos(x, 3)
  707. monitor.write(msg)
  708. else
  709. local msg = string.format("Next scan in %ds", scanTimeLeft)
  710. local x = math.floor((w - #msg) / 2)
  711. if x < 1 then x = 1 end
  712. monitor.setCursorPos(x, 3)
  713. monitor.write(msg)
  714. end
  715. end
  716.  
  717. local function runScan()
  718. scanTimeLeft = SCAN_INTERVAL
  719. equipmentData, builderData, nonbuilderData = processRequests()
  720. local all = flattenData(equipmentData, builderData, nonbuilderData)
  721. if #all == 0 then
  722. displayHappyDance()
  723. elseif not detailMode then
  724. displayTileMenu(all)
  725. end
  726. end
  727.  
  728. drawBanner()
  729. displayLegend()
  730. runScan()
  731.  
  732. local function countdownLoop()
  733. while true do
  734. sleep(1)
  735. if detailMode then
  736. detailTimeLeft = math.max(0, detailTimeLeft - 1)
  737. if detailTimeLeft <= 0 then
  738. detailMode = false
  739. detailRequest = nil
  740. detailTimeLeft = 0
  741. local all = flattenData(equipmentData, builderData, nonbuilderData)
  742. displayTileMenu(all)
  743. end
  744. else
  745. scanTimeLeft = math.max(0, scanTimeLeft - 1)
  746. if scanTimeLeft <= 0 then
  747. runScan()
  748. scanTimeLeft = SCAN_INTERVAL
  749. end
  750. end
  751. updateCountdownLine()
  752. end
  753. end
  754.  
  755. local function eventLoop()
  756. while true do
  757. local ev = { os.pullEvent() }
  758. if ev[1] == "monitor_touch" then
  759. local _, touchX, touchY = ev[2], ev[3], ev[4]
  760. if detailMode then
  761. if detailToggleButton and touchY == detailToggleButton.y then
  762. toggleSkipOption(detailRequest)
  763. detailRequest.skip = not detailRequest.skip
  764. if detailRequest.skip then
  765. detailRequest.skipReason = "Manually added skip pattern: " .. detailRequest.name
  766. else
  767. detailRequest.skipReason = nil
  768. end
  769. detailTimeLeft = DETAIL_TIMEOUT
  770. displayDetailScreen(detailRequest)
  771. updateCountdownLine()
  772. else
  773. detailMode = false
  774. detailRequest = nil
  775. detailTimeLeft = 0
  776. local all = flattenData(equipmentData, builderData, nonbuilderData)
  777. displayTileMenu(all)
  778. updateCountdownLine()
  779. end
  780. else
  781. for _, tile in ipairs(tilePositions) do
  782. if touchX >= tile.x1 and touchX <= tile.x2 and touchY >= tile.y1 and touchY <= tile.y2 then
  783. displayDetailScreen(tile.requestRef)
  784. detailMode = true
  785. detailTimeLeft = DETAIL_TIMEOUT
  786. updateCountdownLine()
  787. break
  788. end
  789. end
  790. end
  791. end
  792. end
  793. end
  794.  
  795. parallel.waitForAny(countdownLoop, eventLoop)
  796. ------------------------------------------------------------
  797. -- End of AE2ColonyWarehouse Script
  798. ------------------------------------------------------------
  799.  
Advertisement
Add Comment
Please, Sign In to add comment