Advertisement
Guest User

Untitled

a guest
Nov 27th, 2023
2,089
1
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 17.05 KB | Gaming | 1 0
  1. -- RSWarehouse.lua
  2. -- Author: Scott Adkins <[email protected]> (Zucanthor)
  3. -- Published: 2021-09-21
  4. --
  5. -- This program monitors work requests for the Minecolonies Warehouse and
  6. -- tries to fulfill requests from the Refined Storage network. If the
  7. -- RS network doesn't have enough items and a crafting pattern exists, a
  8. -- crafting job is scheduled to restock the items in order to fulfill the
  9. -- work request.  The script will continuously loop, monitoring for new
  10. -- requests and checking on crafting jobs to fulfill previous requests.
  11.  
  12. -- The following is required for setup:
  13. --   * 1 ComputerCraft Computer
  14. --   * 1 or more ComputerCraft Monitors (recommend 3x3 monitors)
  15. --   * 1 Advanced Peripheral Colony Integrator
  16. --   * 1 Advanced Peripheral RS Bridge
  17. --   * 1 Chest or other storage container
  18. -- Attach an RS Cable from the RS network to the RS Bridge. Connect the
  19. -- storage container to the Minecolonies Warehouse Hut block. One idea is
  20. -- to set up a second RS network attached to the Warehouse Hut using an
  21. -- External Storage connector and then attach an Importer for that network
  22. -- to the storage container.
  23.  
  24. -- THINGS YOU CAN CUSTOMIZE IN THIS PROGRAM:
  25. -- Line 59: Specify the side storage container is at.
  26. -- Line 66: Name of log file for storing JSON data of all open requests.
  27. -- Lines 231+: Any items you find that should be manually provided.
  28. -- Line 373: Time in seconds between work order scans.
  29.  
  30. ----------------------------------------------------------------------------
  31. -- INITIALIZATION
  32. ----------------------------------------------------------------------------
  33.  
  34. -- Initialize Monitor
  35. -- A future update may allow for multiple monitors. This would allow one
  36. -- monitor to be used for logging and another to be used for work requests.
  37. local monitor = peripheral.find("monitor")
  38. if not monitor then error("Monitor not found.") end
  39. monitor.setTextScale(0.5)
  40. monitor.clear()
  41. monitor.setCursorPos(1, 1)
  42. monitor.setCursorBlink(false)
  43. print("Monitor initialized.")
  44.  
  45. -- Initialize RS Bridge
  46. local bridge = peripheral.find("rsBridge")
  47. if not bridge then error("RS Bridge not found.") end
  48. print("RS Bridge initialized.")
  49.  
  50. -- Initialize Colony Integrator
  51. local colony = peripheral.find("colonyIntegrator")
  52. if not colony then error("Colony Integrator not found.") end
  53. if not colony.isInColony then error("Colony Integrator is not in a colony.") end
  54. print("Colony Integrator initialized.")
  55.  
  56. -- Point to location of chest or storage container
  57. -- A future update may autodetect where the storage container is and error
  58. -- out if no storage container is found.
  59. local storage = "right"
  60. print("Storage initialized.")
  61.  
  62. -- Name of log file to capture JSON data from the open requests.  The log can
  63. -- be too big to edit within CC, which may require a "pastebin put" if you want
  64. -- to look at it.  Logging could be improved to only capture Skipped items,
  65. -- which in turn will make log files smaller and edittable in CC directly.
  66. local logFile = "RSWarehouse.log"
  67.  
  68. ----------------------------------------------------------------------------
  69. -- FUNCTIONS
  70. ----------------------------------------------------------------------------
  71.  
  72. -- Prints to the screen one row after another, scrolling the screen when
  73. -- reaching the bottom. Acts as a normal display where text is printed in
  74. -- a standard way. Long lines are not wrapped and newlines are printed as
  75. -- spaces, both to be addressed in a future update.
  76. -- NOTE: No longer used in this program.
  77. function mPrintScrollable(mon, ...)
  78.     local w, h = mon.getSize()
  79.     local x, y = mon.getCursorPos()
  80.  
  81.     -- Blink the cursor like a normal display.
  82.     mon.setCursorBlink(true)
  83.  
  84.     local t = arg[1]  -- Initialize t with the first argument
  85.     -- For multiple strings, append them with a space between each.
  86.     for i = 2, select("#", ...) do
  87.         t = t .. " " .. select(i, ...)
  88.     end
  89.  
  90.     mon.write(t)
  91.    
  92.     if y >= h then
  93.         mon.scroll(1)
  94.         mon.setCursorPos(1, y)
  95.     else
  96.         mon.setCursorPos(1, y + 1)
  97.     end
  98. end
  99.  
  100.  
  101. -- Prints strings left, centered, or right justified at a specific row and
  102. -- specific foreground/background color.
  103. function mPrintRowJustified(mon, y, pos, text, ...)
  104.     local w, h = mon.getSize()
  105.     local fg = mon.getTextColor()
  106.     local bg = mon.getBackgroundColor()
  107.  
  108.     local x
  109.     if pos == "left" then
  110.         x = 1
  111.     elseif pos == "center" then
  112.         x = math.floor((w - #text) / 2)
  113.     elseif pos == "right" then
  114.         x = w - #text
  115.     end
  116.  
  117.     if select("#", ...) > 0 then
  118.         mon.setTextColor(select(1, ...))
  119.     end
  120.     if select("#", ...) > 1 then
  121.         mon.setBackgroundColor(select(2, ...))
  122.     end
  123.  
  124.     mon.setCursorPos(x, y)
  125.     mon.write(text)
  126.     mon.setTextColor(fg)
  127.     mon.setBackgroundColor(bg)
  128. end
  129.  
  130.  
  131. -- Utility function that returns true if the provided character is a digit.
  132. -- Yes, this is a hack and there are better ways to do this.  Clearly.
  133. function isdigit(c)
  134.     if c == "0" then return true end
  135.     if c == "1" then return true end
  136.     if c == "2" then return true end
  137.     if c == "3" then return true end
  138.     if c == "4" then return true end
  139.     if c == "5" then return true end
  140.     if c == "6" then return true end
  141.     if c == "7" then return true end
  142.     if c == "8" then return true end
  143.     if c == "9" then return true end
  144.     return false
  145. end
  146.  
  147. -- Utility function that displays current time and remaining time on timer.
  148. -- For time of day, yellow is day, orange is sunset/sunrise, and red is night.
  149. -- The countdown timer is yellow over 15s, orange under 15s, and red under 5s.
  150. -- At night, the countdown timer is red and shows PAUSED insted of a time.
  151. function displayTimer(mon, t)
  152.     now = os.time()
  153.  
  154.     cycle = "day"
  155.     cycle_color = colors.orange
  156.     if now >= 4 and now < 6 then
  157.         cycle = "sunrise"
  158.         cycle_color = colors.orange
  159.     elseif now >= 6 and now < 18 then
  160.         cycle = "day"
  161.         cycle_color = colors.yellow
  162.     elseif now >= 18 and now < 19.5 then
  163.         cycle = "sunset"
  164.         cycle_color = colors.orange
  165.     elseif now >= 19.5 or now < 5 then
  166.         cycle = "night"
  167.         cycle_color = colors.red
  168.     end
  169.  
  170.     timer_color = colors.orange
  171.     if t < 15 then timer_color = colors.yellow end
  172.     if t < 5 then timer_color = colors.red end
  173.  
  174.     mPrintRowJustified(mon, 1, "left", string.format("Time: %s [%s]    ", textutils.formatTime(now, false), cycle), cycle_color)
  175.     if cycle ~= "night" then mPrintRowJustified(mon, 1, "right", string.format("    Remaining: %ss", t), timer_color)
  176.     else mPrintRowJustified(mon, 1, "right", "    Remaining: PAUSED", colors.red) end
  177. end
  178.  
  179. -- Scan all open work requests from the Warehouse and attempt to satisfy those
  180. -- requests.  Display all activity on the monitor, including time of day and the
  181. -- countdown timer before next scan.  This function is not called at night to
  182. -- save on some ticks, as the colonists are in bed anyways.  Items in red mean
  183. -- work order can't be satisfied by Refined Storage (lack of pattern or lack of
  184. -- required crafting ingredients).  Yellow means order partially filled and a
  185. -- crafting job was scheduled for the rest.  Green means order fully filled.
  186. -- Blue means the Player needs to manually fill the work order.  This includes
  187. -- equipment (Tools of Class), NBT items like armor, weapons and tools, as well
  188. -- as generic requests ike Compostables, Fuel, Food, Flowers, etc.
  189. function scanWorkRequests(mon, rs, chest)
  190.     -- Before we do anything, prep the log file for this scan.
  191.     -- The log file is truncated each time this function is called.
  192.     file = fs.open(logFile, "w")
  193.     print("\nScan starting at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").")
  194.  
  195.     -- We want to keep three different lists so that they can be
  196.     -- displayed on the monitor in a more intelligent way.  The first
  197.     -- list is for the Builder requests.  The second list is for the
  198.     -- non-Builder requests.  The third list is for any armor, tools
  199.     -- and weapons requested by the colonists.
  200.     builder_list = {}
  201.     nonbuilder_list = {}
  202.     equipment_list = {}
  203.  
  204.     -- Scan RS for all items in its network. Ignore items with NBT data.
  205.     -- If a Builder needs any items with NBT data, this function will need
  206.     -- to be updated to not ignore those items.
  207.     items = rs.listItems()
  208.     item_array = {}
  209.     for index, item in ipairs(items) do
  210.         if not item.nbt then
  211.             item_array[item.name] = item.amount
  212.         end
  213.     end
  214.  
  215.     -- Scan the Warehouse for all open work requests. For each item, try to
  216.     -- provide as much as possible from RS, then craft whatever is needed
  217.     -- after that. Green means item was provided entirely. Yellow means item
  218.     -- is being crafted. Red means item is missing crafting recipe.
  219.     workRequests = colony.getRequests()
  220.     file.write(textutils.serialize(workRequests, { allow_repetitions = true } ))
  221.     for w in pairs(workRequests) do
  222.         name = workRequests[w].name
  223.         item = workRequests[w].items[1].name
  224.         target = workRequests[w].target
  225.         desc = workRequests[w].desc
  226.         needed = workRequests[w].count
  227.         provided = 0
  228.  
  229.         target_words = {}
  230.         target_length = 0
  231.         for word in target:gmatch("%S+") do
  232.             table.insert(target_words, word)
  233.             target_length = target_length + 1
  234.         end
  235.  
  236.         if target_length >= 3 then target_name = target_words[target_length-2] .. " " .. target_words[target_length]
  237.         else target_name = target end
  238.  
  239.         target_type = ""
  240.         target_count = 1
  241.         repeat
  242.             if target_type ~= "" then target_type = target_type .. " " end
  243.             target_type = target_type .. target_words[target_count]
  244.             target_count = target_count + 1
  245.         until target_count > target_length - 3
  246.  
  247.         useRS = 1
  248.         if string.find(desc, "Tool of class") then useRS = 0 end
  249.         if string.find(name, "Hoe") then useRS = 0 end
  250.         if string.find(name, "Shovel") then useRS = 0 end
  251.         if string.find(name, "Axe") then useRS = 0 end
  252.         if string.find(name, "Pickaxe") then useRS = 0 end
  253.         if string.find(name, "Bow") then useRS = 0 end
  254.         if string.find(name, "Sword") then useRS = 0 end
  255.         if string.find(name, "Shield") then useRS = 0 end
  256.         if string.find(name, "Helmet") then useRS = 0 end
  257.         if string.find(name, "Leather Cap") then useRS = 0 end
  258.         if string.find(name, "Chestplate") then useRS = 0 end
  259.         if string.find(name, "Tunic") then useRS = 0 end
  260.         if string.find(name, "Pants") then useRS = 0 end
  261.         if string.find(name, "Leggings") then useRS = 0 end
  262.         if string.find(name, "Boots") then useRS = 0 end
  263.         if name == "Rallying Banner" then useRS = 0 end --bugged in alpha versions
  264.         if name == "Crafter" then useRS = 0 end
  265.         if name == "Compostable" then useRS = 0 end
  266.         if name == "Fertilizer" then useRS = 0 end
  267.         if name == "Flowers" then useRS = 0 end
  268.         if name == "Food" then useRS = 0 end
  269.         if name == "Fuel" then useRS = 0 end
  270.         if name == "Smeltable Ore" then useRS = 0 end
  271.         if name == "Stack List" then useRS = 0 end
  272.  
  273.         color = colors.blue
  274.         if useRS == 1 then
  275.             if item_array[item] then
  276.                 provided = rs.exportItemToPeripheral({name=item, count=needed}, chest)
  277.             end
  278.  
  279.             color = colors.green
  280.             if provided < needed then
  281.                 if rs.isItemCrafting( { name = item } ) then
  282.                     color = colors.yellow
  283.                     print("[Crafting]", item)
  284.                 else
  285.                     if rs.craftItem({name=item, count=needed}) then
  286.                         color = colors.yellow
  287.                         print("[Scheduled]", needed, "x", item)
  288.                     else
  289.                         color = colors.red
  290.                         print("[Failed]", item)
  291.                     end
  292.                 end
  293.             end
  294.         else
  295.             nameString = name .. " [" .. target .. "]"
  296.             print("[Skipped]", nameString)
  297.         end
  298.  
  299.         if string.find(desc, "of class") then
  300.             level = "Any Level"
  301.             if string.find(desc, "with maximal level:Leather") then level = "Leather" end
  302.             if string.find(desc, "with maximal level:Gold") then level = "Gold" end
  303.             if string.find(desc, "with maximal level:Chain") then level = "Chain" end
  304.             if string.find(desc, "with maximal level:Wood or Gold") then level = "Wood or Gold" end
  305.             if string.find(desc, "with maximal level:Stone") then level = "Stone" end
  306.             if string.find(desc, "with maximal level:Iron") then level = "Iron" end
  307.             if string.find(desc, "with maximal level:Diamond") then level = "Diamond" end
  308.             new_name = level .. " " .. name
  309.             if level == "Any Level" then new_name = name .. " of any level" end
  310.             new_target = target_type .. " " .. target_name
  311.             equipment = { name=new_name, target=new_target, needed=needed, provided=provided, color=color}
  312.             table.insert(equipment_list, equipment)
  313.         elseif string.find(target, "Builder") then
  314.             builder = { name=name, item=item, target=target_name, needed=needed, provided=provided, color=color }
  315.             table.insert(builder_list, builder)
  316.         else
  317.             new_target = target_type .. " " .. target_name
  318.             if target_length < 3 then
  319.                 new_target = target
  320.             end
  321.             nonbuilder = { name=name, target=new_target, needed=needed, provided=provided, color=color }
  322.             table.insert(nonbuilder_list, nonbuilder)
  323.         end
  324.     end
  325.  
  326.     -- Show the various lists on the attached monitor.
  327.     row = 3
  328.     mon.clear()
  329.  
  330.     header_shown = 0
  331.     for e in pairs(equipment_list) do
  332.         equipment = equipment_list[e]
  333.         if header_shown == 0 then
  334.             mPrintRowJustified(mon, row, "center", "Equipment")
  335.             header_shown = 1
  336.             row = row + 1
  337.         end
  338.         text = string.format("%d %s", equipment.needed, equipment.name)
  339.         mPrintRowJustified(mon, row, "left", text, equipment.color)
  340.         mPrintRowJustified(mon, row, "right", " " .. equipment.target, equipment.color)
  341.         row = row + 1
  342.     end
  343.  
  344.     header_shown = 0
  345.     for b in pairs(builder_list) do
  346.         builder = builder_list[b]
  347.         if header_shown == 0 then
  348.             if row > 1 then row = row + 1 end
  349.             mPrintRowJustified(mon, row, "center", "Builder Requests")
  350.             header_shown = 1
  351.             row = row + 1
  352.         end
  353.         text = string.format("%d/%s", builder.provided, builder.name)
  354.         mPrintRowJustified(mon, row, "left", text, builder.color)
  355.         mPrintRowJustified(mon, row, "right", " " .. builder.target, builder.color)
  356.         row = row + 1
  357.     end
  358.  
  359.     header_shown = 0
  360.     for n in pairs(nonbuilder_list) do
  361.         nonbuilder = nonbuilder_list[n]
  362.         if header_shown == 0 then
  363.             if row > 1 then row = row + 1 end
  364.             mPrintRowJustified(mon, row, "center", "Nonbuilder Requests")
  365.             header_shown = 1
  366.             row = row + 1
  367.         end
  368.         text = string.format("%d %s", nonbuilder.needed, nonbuilder.name)
  369.         if isdigit(nonbuilder.name:sub(1,1)) then
  370.             text = string.format("%d/%s", nonbuilder.provided, nonbuilder.name)
  371.         end
  372.         mPrintRowJustified(mon, row, "left", text, nonbuilder.color)
  373.         mPrintRowJustified(mon, row, "right", " " .. nonbuilder.target, nonbuilder.color)
  374.         row = row + 1
  375.     end
  376.  
  377.     if row == 3 then mPrintRowJustified(mon, row, "center", "No Open Requests") end
  378.     print("Scan completed at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").")
  379.     file.close()
  380. end
  381.  
  382. ----------------------------------------------------------------------------
  383. -- MAIN
  384. ----------------------------------------------------------------------------
  385.  
  386. -- Scan for requests periodically. This will catch any updates that were
  387. -- triggered from the previous scan. Right-clicking on the monitor will
  388. -- trigger an immediate scan and reset the timer. Unfortunately, there is
  389. -- no way to capture left-clicks on the monitor.
  390. local time_between_runs = 15
  391. local current_run = time_between_runs
  392. scanWorkRequests(monitor, bridge, storage)
  393. displayTimer(monitor, current_run)
  394. local TIMER = os.startTimer(1)
  395.  
  396. while true do
  397.     local e = {os.pullEvent()}
  398.     if e[1] == "timer" and e[2] == TIMER then
  399.         now = os.time()
  400.         if now >= 5 and now < 19.5 then
  401.             current_run = current_run - 1
  402.             if current_run <= 0 then
  403.                 scanWorkRequests(monitor, bridge, storage)
  404.                 current_run = time_between_runs
  405.             end
  406.         end
  407.         displayTimer(monitor, current_run)
  408.         TIMER = os.startTimer(1)
  409.     elseif e[1] == "monitor_touch" then
  410.         os.cancelTimer(TIMER)
  411.         scanWorkRequests(monitor, bridge, storage)
  412.         current_run = time_between_runs
  413.         displayTimer(monitor, current_run)
  414.         TIMER = os.startTimer(1)
  415.     end
  416. end
  417.  
Advertisement
Comments
Add Comment
Please, Sign In to add comment
Advertisement