Q11x

AE2 Minecolonies integration

Jul 20th, 2025 (edited)
314
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 17.82 KB | Gaming | 0 0
  1. -- Original author: Scott Adkins <[email protected]> (Zucanthor)
  2.  
  3. --
  4.  
  5. -- This program monitors work requests for the Minecolonies Warehouse and
  6.  
  7. -- tries to fulfill requests from the Applied Energistics 2 network. If the
  8.  
  9. -- AE2 network doesn't have enough items and a crafting pattern exists, a
  10.  
  11. -- crafting job is scheduled to restock the items in order to fulfill the
  12.  
  13. -- work request.  The script will continuously loop, monitoring for new
  14.  
  15. -- requests and checking on crafting jobs to fulfill previous requests.
  16.  
  17.  
  18.  
  19. -- The following is required for setup:
  20.  
  21. --   * 1 ComputerCraft Computer
  22.  
  23. --   * 1 or more ComputerCraft Monitors (recommend 3x3 advanced monitors)
  24.  
  25. --   * 1 Advanced Peripheral Colony Integrator
  26.  
  27. --   * 1 Advanced Peripheral AE2 Bridge
  28.  
  29. --   * 1 Chest or other storage container
  30.  
  31. -- Attach an AE2 Cable from the AE2 network to the AE2 Bridge. Connect the
  32.  
  33. -- storage container to the Minecolonies Warehouse Hut block directly or
  34.  
  35. -- to an ender chest and then pipe ender chest contents out to the warehouse
  36.  
  37. -- racks. Latter makes it easier to upgrade warehouse.
  38.  
  39.  
  40.  
  41. -- THINGS YOU CAN CUSTOMIZE IN THIS PROGRAM:
  42.  
  43. -- Line 56: Specify the side storage container is at.
  44.  
  45. -- Line 66: Name of log file for storing JSON data of all open requests.
  46.  
  47. -- Lines 231+: Any items you find that should be manually provided.
  48.  
  49. -- Line 373: Time in seconds between work order scans.
  50.  
  51.  
  52.  
  53. ----------------------------------------------------------------------------
  54.  
  55. -- INITIALIZATION
  56.  
  57. ----------------------------------------------------------------------------
  58.  
  59.  
  60.  
  61. -- Initialize Monitor
  62.  
  63. -- A future update may allow for multiple monitors. This would allow one
  64.  
  65. -- monitor to be used for logging and another to be used for work requests.
  66.  
  67. local monitor = peripheral.find("monitor")
  68.  
  69. if not monitor then error("Monitor not found.") end
  70.  
  71. monitor.setTextScale(0.5)
  72.  
  73. monitor.clear()
  74.  
  75. monitor.setCursorPos(1, 1)
  76.  
  77. monitor.setCursorBlink(false)
  78.  
  79. print("Monitor initialized.")
  80.  
  81.  
  82.  
  83. -- Initialize ME Bridge
  84.  
  85. local bridge = peripheral.find("me_bridge")
  86.  
  87. if not bridge then error("ME Bridge not found.") end
  88.  
  89. print("ME Bridge initialized.")
  90.  
  91.  
  92.  
  93. -- Initialize Colony Integrator
  94.  
  95. local colony = peripheral.find("colony_integrator")
  96.  
  97. if not colony then error("Colony Integrator not found.") end
  98.  
  99. if not colony.isInColony then error("Colony Integrator is not in a colony.") end
  100.  
  101. print("Colony Integrator initialized.")
  102.  
  103.  
  104.  
  105. -- Point to location of chest or storage container
  106.  
  107. -- A future update may autodetect where the storage container is and error
  108.  
  109. -- out if no storage container is found.
  110.  
  111. local storage = "left"
  112.  
  113. print("Storage initialized.")
  114.  
  115.  
  116.  
  117. -- Name of log file to capture JSON data from the open requests.  The log can
  118.  
  119. -- be too big to edit within CC, which may require a "pastebin put" if you want
  120.  
  121. -- to look at it.  Logging could be improved to only capture Skipped items,
  122.  
  123. -- which in turn will make log files smaller and edittable in CC directly.
  124.  
  125. local logFile = "AEWarehouse.log"
  126.  
  127.  
  128.  
  129. ----------------------------------------------------------------------------
  130.  
  131. -- FUNCTIONS
  132.  
  133. ----------------------------------------------------------------------------
  134.  
  135.  
  136.  
  137. -- Prints to the screen one row after another, scrolling the screen when
  138.  
  139. -- reaching the bottom. Acts as a normal display where text is printed in
  140.  
  141. -- a standard way. Long lines are not wrapped and newlines are printed as
  142.  
  143. -- spaces, both to be addressed in a future update.
  144.  
  145. -- NOTE: No longer used in this program.
  146.  
  147. function mPrintScrollable(mon, ...)
  148.  
  149.     w, h = mon.getSize()
  150.  
  151.     x, y = mon.getCursorPos()
  152.  
  153.  
  154.  
  155.     -- Blink the cursor like a normal display.
  156.  
  157.     mon.setCursorBlink(true)
  158.  
  159.  
  160.  
  161.     -- For multiple strings, append them with a space between each.
  162.  
  163.     for i = 2, #arg do t = t.." "..arg[i] end
  164.  
  165.     mon.write(arg[1])
  166.  
  167.     if y >= h then
  168.  
  169.         mon.scroll(1)
  170.  
  171.         mon.setCursorPos(1, y)
  172.  
  173.     else
  174.  
  175.         mon.setCursorPos(1, y+1)
  176.  
  177.     end
  178.  
  179. end
  180.  
  181.  
  182.  
  183. -- Prints strings left, centered, or right justified at a specific row and
  184.  
  185. -- specific foreground/background color.
  186.  
  187. function mPrintRowJustified(mon, y, pos, text, ...)
  188.  
  189.     w, h = mon.getSize()
  190.  
  191.     fg = mon.getTextColor()
  192.  
  193.     bg = mon.getBackgroundColor()
  194.  
  195.  
  196.  
  197.     if pos == "left" then x = 1 end
  198.  
  199.     if pos == "center" then x = math.floor((w - #text) / 2) end
  200.  
  201.     if pos == "right" then x = w - #text end
  202.  
  203.  
  204.  
  205.     if #arg > 0 then mon.setTextColor(arg[1]) end
  206.  
  207.     if #arg > 1 then mon.setBackgroundColor(arg[2]) end
  208.  
  209.     mon.setCursorPos(x, y)
  210.  
  211.     mon.write(text)
  212.  
  213.     mon.setTextColor(fg)
  214.  
  215.     mon.setBackgroundColor(bg)
  216.  
  217. end
  218.  
  219.  
  220.  
  221. -- Utility function that returns true if the provided character is a digit.
  222.  
  223. -- Yes, this is a hack and there are better ways to do this.  Clearly.
  224.  
  225. function isdigit(c)
  226.  
  227.     if c == "0" then return true end
  228.  
  229.     if c == "1" then return true end
  230.  
  231.     if c == "2" then return true end
  232.  
  233.     if c == "3" then return true end
  234.  
  235.     if c == "4" then return true end
  236.  
  237.     if c == "5" then return true end
  238.  
  239.     if c == "6" then return true end
  240.  
  241.     if c == "7" then return true end
  242.  
  243.     if c == "8" then return true end
  244.  
  245.     if c == "9" then return true end
  246.  
  247.     return false
  248.  
  249. end
  250.  
  251.  
  252.  
  253. -- Utility function that displays current time and remaining time on timer.
  254.  
  255. -- For time of day, yellow is day, orange is sunset/sunrise, and red is night.
  256.  
  257. -- The countdown timer is orange over 15s, yellow under 15s, and red under 5s.
  258.  
  259. -- At night, the countdown timer is red and shows PAUSED insted of a time.
  260.  
  261. function displayTimer(mon, t)
  262.  
  263.     now = os.time()
  264.  
  265.  
  266.  
  267.     cycle = "day"
  268.  
  269.     cycle_color = colors.orange
  270.  
  271.     if now >= 4 and now < 6 then
  272.  
  273.         cycle = "sunrise"
  274.  
  275.         cycle_color = colors.orange
  276.  
  277.     elseif now >= 6 and now < 18 then
  278.  
  279.         cycle = "day"
  280.  
  281.         cycle_color = colors.yellow
  282.  
  283.     elseif now >= 18 and now < 19.5 then
  284.  
  285.         cycle = "sunset"
  286.  
  287.         cycle_color = colors.orange
  288.  
  289.     elseif now >= 19.5 or now < 5 then
  290.  
  291.         cycle = "night"
  292.  
  293.         cycle_color = colors.red
  294.  
  295.     end
  296.  
  297.  
  298.  
  299.     timer_color = colors.orange
  300.  
  301.     if t < 15 then timer_color = colors.yellow end
  302.  
  303.     if t < 5 then timer_color = colors.red end
  304.  
  305.  
  306.  
  307.     mPrintRowJustified(mon, 1, "left", string.format("Time: %s [%s]    ", textutils.formatTime(now, false), cycle), cycle_color)
  308.  
  309.     if cycle ~= "night" then mPrintRowJustified(mon, 1, "right", string.format("    Remaining: %ss", t), timer_color)
  310.  
  311.     else mPrintRowJustified(mon, 1, "right", "    Remaining: PAUSED", colors.red) end
  312.  
  313. end
  314.  
  315.  
  316.  
  317. -- Scan all open work requests from the Warehouse and attempt to satisfy those
  318.  
  319. -- requests.  Display all activity on the monitor, including time of day and the
  320.  
  321. -- countdown timer before next scan.  This function is not called at night to
  322.  
  323. -- save on some ticks, as the colonists are in bed anyways.  Items in red mean
  324.  
  325. -- work order can't be satisfied by Refined Storage (lack of pattern or lack of
  326.  
  327. -- required crafting ingredients).  Yellow means order partially filled and a
  328.  
  329. -- crafting job was scheduled for the rest.  Green means order fully filled.
  330.  
  331. -- Blue means the Player needs to manually fill the work order.  This includes
  332.  
  333. -- equipment (Tools of Class), NBT items like armor, weapons and tools, as well
  334.  
  335. -- as generic requests ike Compostables, Fuel, Food, Flowers, etc.
  336.  
  337. function scanWorkRequests(mon, rs, chest)
  338.  
  339.     -- Before we do anything, prep the log file for this scan.
  340.  
  341.     -- The log file is truncated each time this function is called.
  342.  
  343.     file = fs.open(logFile, "w")
  344.  
  345.     print("\nScan starting at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").")
  346.  
  347.  
  348.  
  349.     -- We want to keep three different lists so that they can be
  350.  
  351.     -- displayed on the monitor in a more intelligent way.  The first
  352.  
  353.     -- list is for the Builder requests.  The second list is for the
  354.  
  355.     -- non-Builder requests.  The third list is for any armor, tools
  356.  
  357.     -- and weapons requested by the colonists.
  358.  
  359.     builder_list = {}
  360.  
  361.     nonbuilder_list = {}
  362.  
  363.     equipment_list = {}
  364.  
  365.  
  366.  
  367.     -- Scan RS for all items in its network. Ignore items with NBT data.
  368.  
  369.     -- If a Builder needs any items with NBT data, this function will need
  370.  
  371.     -- to be updated to not ignore those items.
  372.  
  373.     items = bridge.listItems()
  374.  
  375.     item_array = {}
  376.  
  377.     for index, item in pairs(items) do
  378.  
  379.         if not item.nbt then
  380.  
  381.             item_array[item.name] = item.count
  382.  
  383.         end
  384.  
  385.     end
  386.  
  387.    
  388.  
  389.     local count = 0
  390.  
  391.     for k, v in pairs(item_array) do
  392.  
  393.       count = count + 1
  394.  
  395.     end
  396.  
  397.  
  398.  
  399.     print("Unique item count in system:", count)
  400.  
  401.  
  402.  
  403.     -- Scan the Warehouse for all open work requests. For each item, try to
  404.  
  405.     -- provide as much as possible from RS, then craft whatever is needed
  406.  
  407.     -- after that. Green means item was provided entirely. Yellow means item
  408.  
  409.     -- is being crafted. Red means item is missing crafting recipe.
  410.  
  411.     workRequests = colony.getRequests()
  412.  
  413.     -- file.write(textutils.serialize(workRequests))
  414.  
  415.     for w in pairs(workRequests) do
  416.  
  417.         name = workRequests[w].name
  418.  
  419.         item = workRequests[w].items[1].name
  420.  
  421.         target = workRequests[w].target
  422.  
  423.         desc = workRequests[w].desc
  424.  
  425.         needed = workRequests[w].count
  426.  
  427.         provided = 0
  428.  
  429.  
  430.  
  431.         target_words = {}
  432.  
  433.         target_length = 0
  434.  
  435.         for word in target:gmatch("%S+") do
  436.  
  437.             table.insert(target_words, word)
  438.  
  439.             target_length = target_length + 1
  440.  
  441.         end
  442.  
  443.  
  444.  
  445.         if target_length >= 3 then target_name = target_words[target_length-2] .. " " .. target_words[target_length]
  446.  
  447.         else target_name = target end
  448.  
  449.  
  450.  
  451.         target_type = ""
  452.  
  453.         target_count = 1
  454.  
  455.         repeat
  456.  
  457.             if target_type ~= "" then target_type = target_type .. " " end
  458.  
  459.             target_type = target_type .. target_words[target_count]
  460.  
  461.             target_count = target_count + 1
  462.  
  463.         until target_count > target_length - 3
  464.  
  465.  
  466.  
  467.         useRS = 1
  468.  
  469.         if string.find(desc, "Tool of class") then useRS = 0 end
  470.  
  471.         if string.find(name, "Hoe") then useRS = 0 end
  472.  
  473.         if string.find(name, "Shovel") then useRS = 0 end
  474.  
  475.         if string.find(name, "Axe") then useRS = 0 end
  476.  
  477.         if string.find(name, "Pickaxe") then useRS = 0 end
  478.  
  479.         if string.find(name, "Bow") then useRS = 0 end
  480.  
  481.         if string.find(name, "Sword") then useRS = 0 end
  482.  
  483.         if string.find(name, "Shield") then useRS = 0 end
  484.  
  485.         if string.find(name, "Helmet") then useRS = 0 end
  486.  
  487.         if string.find(name, "Leather Cap") then useRS = 0 end
  488.  
  489.         if string.find(name, "Chestplate") then useRS = 0 end
  490.  
  491.         if string.find(name, "Tunic") then useRS = 0 end
  492.  
  493.         if string.find(name, "Pants") then useRS = 0 end
  494.  
  495.         if string.find(name, "Leggings") then useRS = 0 end
  496.  
  497.         if string.find(name, "Boots") then useRS = 0 end
  498.  
  499.         if name == "Rallying Banner" then useRS = 0 end --bugged in alpha versions
  500.  
  501.         if name == "Crafter" then useRS = 0 end
  502.  
  503.         if name == "Compostable" then useRS = 0 end
  504.  
  505.         if name == "Fertilizer" then useRS = 0 end
  506.  
  507.         if name == "Flowers" then useRS = 0 end
  508.  
  509.         if name == "Food" then useRS = 0 end
  510.  
  511.         if name == "Fuel" then useRS = 0 end
  512.  
  513.         if name == "Smeltable Ore" then useRS = 0 end
  514.  
  515.         if name == "Stack List" then useRS = 0 end
  516.  
  517.  
  518.  
  519.         color = colors.blue
  520.  
  521.         if useRS == 1 then
  522.  
  523.            
  524.  
  525.             if item_array[item] then
  526.  
  527.                 provided = bridge.exportItemToPeripheral({name=item, count=needed}, chest)
  528.  
  529.                 print(string.format("[Export] Attempted %d x %s; Provided %d", needed, item, provided))
  530.  
  531.             end
  532.  
  533.  
  534.  
  535.             color = colors.green
  536.  
  537.             if provided < needed then
  538.  
  539.                 if bridge.isItemCrafting( { name = item } ) then
  540.  
  541.                     color = colors.yellow
  542.  
  543.                     print("[Crafting]", item)
  544.  
  545.                 else
  546.  
  547.                     if bridge.craftItem({name=item, count=needed}) then
  548.  
  549.                         color = colors.yellow
  550.  
  551.                         print("[Scheduled]", needed, "x", item)
  552.  
  553.                     else
  554.  
  555.                         color = colors.red
  556.  
  557.                         print("[Failed]", item)
  558.  
  559.                     end
  560.  
  561.                 end
  562.  
  563.             end
  564.  
  565.         else
  566.  
  567.             nameString = name .. " [" .. target .. "]"
  568.  
  569.             print("[Skipped]", nameString)
  570.  
  571.         end
  572.  
  573.  
  574.  
  575.         if string.find(desc, "of class") then
  576.  
  577.             level = "Any Level"
  578.  
  579.             if string.find(desc, "with maximal level:Leather") then level = "Leather" end
  580.  
  581.             if string.find(desc, "with maximal level:Gold") then level = "Gold" end
  582.  
  583.             if string.find(desc, "with maximal level:Chain") then level = "Chain" end
  584.  
  585.             if string.find(desc, "with maximal level:Wood or Gold") then level = "Wood or Gold" end
  586.  
  587.             if string.find(desc, "with maximal level:Stone") then level = "Stone" end
  588.  
  589.             if string.find(desc, "with maximal level:Iron") then level = "Iron" end
  590.  
  591.             if string.find(desc, "with maximal level:Diamond") then level = "Diamond" end
  592.  
  593.             new_name = level .. " " .. name
  594.  
  595.             if level == "Any Level" then new_name = name .. " of any level" end
  596.  
  597.             new_target = target_type .. " " .. target_name
  598.  
  599.             equipment = { name=new_name, target=new_target, needed=needed, provided=provided, color=color}
  600.  
  601.             table.insert(equipment_list, equipment)
  602.  
  603.         elseif string.find(target, "Builder") then
  604.  
  605.             builder = { name=name, item=item, target=target_name, needed=needed, provided=provided, color=color }
  606.  
  607.             table.insert(builder_list, builder)
  608.  
  609.         else
  610.  
  611.             new_target = target_type .. " " .. target_name
  612.  
  613.             if target_length < 3 then
  614.  
  615.                 new_target = target
  616.  
  617.             end
  618.  
  619.             nonbuilder = { name=name, target=new_target, needed=needed, provided=provided, color=color }
  620.  
  621.             table.insert(nonbuilder_list, nonbuilder)
  622.  
  623.         end
  624.  
  625.     end
  626.  
  627.  
  628.  
  629.     -- Show the various lists on the attached monitor.
  630.  
  631.     row = 3
  632.  
  633.     mon.clear()
  634.  
  635.  
  636.  
  637.     header_shown = 0
  638.  
  639.     for e in pairs(equipment_list) do
  640.  
  641.         equipment = equipment_list[e]
  642.  
  643.         if header_shown == 0 then
  644.  
  645.             mPrintRowJustified(mon, row, "center", "Equipment")
  646.  
  647.             header_shown = 1
  648.  
  649.             row = row + 1
  650.  
  651.         end
  652.  
  653.         text = string.format("%d %s", equipment.needed, equipment.name)
  654.  
  655.         mPrintRowJustified(mon, row, "left", text, equipment.color)
  656.  
  657.         mPrintRowJustified(mon, row, "right", " " .. equipment.target, equipment.color)
  658.  
  659.         row = row + 1
  660.  
  661.     end
  662.  
  663.  
  664.  
  665.     header_shown = 0
  666.  
  667.     for b in pairs(builder_list) do
  668.  
  669.         builder = builder_list[b]
  670.  
  671.         if header_shown == 0 then
  672.  
  673.             if row > 1 then row = row + 1 end
  674.  
  675.             mPrintRowJustified(mon, row, "center", "Builder Requests")
  676.  
  677.             header_shown = 1
  678.  
  679.             row = row + 1
  680.  
  681.         end
  682.  
  683.         text = string.format("%d/%s", builder.provided, builder.name)
  684.  
  685.         mPrintRowJustified(mon, row, "left", text, builder.color)
  686.  
  687.         mPrintRowJustified(mon, row, "right", " " .. builder.target, builder.color)
  688.  
  689.         row = row + 1
  690.  
  691.     end
  692.  
  693.  
  694.  
  695.     header_shown = 0
  696.  
  697.     for n in pairs(nonbuilder_list) do
  698.  
  699.         nonbuilder = nonbuilder_list[n]
  700.  
  701.         if header_shown == 0 then
  702.  
  703.             if row > 1 then row = row + 1 end
  704.  
  705.             mPrintRowJustified(mon, row, "center", "Nonbuilder Requests")
  706.  
  707.             header_shown = 1
  708.  
  709.             row = row + 1
  710.  
  711.         end
  712.  
  713.         text = string.format("%d %s", nonbuilder.needed, nonbuilder.name)
  714.  
  715.         if isdigit(nonbuilder.name:sub(1,1)) then
  716.  
  717.             text = string.format("%d/%s", nonbuilder.provided, nonbuilder.name)
  718.  
  719.         end
  720.  
  721.         mPrintRowJustified(mon, row, "left", text, nonbuilder.color)
  722.  
  723.         mPrintRowJustified(mon, row, "right", " " .. nonbuilder.target, nonbuilder.color)
  724.  
  725.         row = row + 1
  726.  
  727.     end
  728.  
  729.  
  730.  
  731.     if row == 3 then mPrintRowJustified(mon, row, "center", "No Open Requests") end
  732.  
  733.     print("Scan completed at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").")
  734.  
  735.     file.close()
  736.  
  737. end
  738.  
  739.  
  740.  
  741. ----------------------------------------------------------------------------
  742.  
  743. -- MAIN
  744.  
  745. ----------------------------------------------------------------------------
  746.  
  747.  
  748.  
  749. -- Scan for requests periodically. This will catch any updates that were
  750.  
  751. -- triggered from the previous scan. Right-clicking on the monitor will
  752.  
  753. -- trigger an immediate scan and reset the timer. Unfortunately, there is
  754.  
  755. -- no way to capture left-clicks on the monitor.
  756.  
  757. local time_between_runs = 30
  758.  
  759. local current_run = time_between_runs
  760.  
  761. scanWorkRequests(monitor, bridge, storage)
  762.  
  763. displayTimer(monitor, current_run)
  764.  
  765. local TIMER = os.startTimer(1)
  766.  
  767.  
  768.  
  769. while true do
  770.  
  771.     local e = {os.pullEvent()}
  772.  
  773.     if e[1] == "timer" and e[2] == TIMER then
  774.  
  775.         now = os.time()
  776.  
  777.         if now >= 5 and now < 19.5 then
  778.  
  779.             current_run = current_run - 1
  780.  
  781.             if current_run <= 0 then
  782.  
  783.                 scanWorkRequests(monitor, bridge, storage)
  784.  
  785.                 current_run = time_between_runs
  786.  
  787.             end
  788.  
  789.         end
  790.  
  791.         displayTimer(monitor, current_run)
  792.  
  793.         TIMER = os.startTimer(1)
  794.  
  795.     elseif e[1] == "monitor_touch" then
  796.  
  797.         os.cancelTimer(TIMER)
  798.  
  799.         scanWorkRequests(monitor, bridge, storage)
  800.  
  801.         current_run = time_between_runs
  802.  
  803.         displayTimer(monitor, current_run)
  804.  
  805.         TIMER = os.startTimer(1)
  806.  
  807.     end
  808.  
  809. end
  810.  
  811.  
Advertisement
Add Comment
Please, Sign In to add comment