Advertisement
gelatine87

RSWarehouse+BuildingStatus

May 9th, 2023
1,090
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 21.86 KB | Gaming | 0 0
  1. -- RSWarehouse.lua
  2. -- Author: Scott Adkins <adkinss@gmail.com> (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, arg.
  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 monitors = {}
  38.  
  39. for _, name in pairs(peripheral.getNames()) do
  40.   if peripheral.getType(name) == "monitor" then
  41.     table.insert(monitors, name)
  42.   end
  43. end
  44.  
  45. local function displayButtons(monitors)
  46.   for i, name in ipairs(monitors) do
  47.     local monitor = peripheral.wrap(name)
  48.     monitor.setTextScale(0.5)
  49.     monitor.setCursorPos(1, 1)
  50.     monitor.write("Press the button to select this as your main Monitor:")
  51.     monitor.setCursorPos(1, 3)
  52.     monitor.write("[ SELECT ]")
  53.   end
  54. end
  55.  
  56. displayButtons(monitors)
  57.  
  58. local selectedMonitor = nil
  59.  
  60. while not selectedMonitor do
  61.   local event, side, x, y = os.pullEvent("monitor_touch")
  62.  
  63.   for i, name in ipairs(monitors) do
  64.     if side == name then
  65.       selectedMonitor = name
  66.       break
  67.     end
  68.   end
  69. end
  70.  
  71. local selectedMonitor = peripheral.wrap(selectedMonitor)
  72. selectedMonitor.setTextScale(0.5)
  73. selectedMonitor.setCursorPos(1, 1)
  74. selectedMonitor.clear()
  75. selectedMonitor.write("Request Monitor")
  76.  
  77. local otherMonitor = nil
  78. for i, name in ipairs(monitors) do
  79.   if name ~= selectedMonitor then
  80.     otherMonitor = name
  81.     break
  82.   end
  83. end
  84.  
  85. otherMonitor = peripheral.wrap(otherMonitor)
  86. otherMonitor.setTextScale(0.5)
  87. otherMonitor.setCursorPos(1, 1)
  88. otherMonitor.clear()
  89. otherMonitor.write("Building Status Monitor")
  90.  
  91. -- Initialize RS Bridge
  92. local bridge = peripheral.find("rsBridge")
  93. if not bridge then error("RS Bridge not found.") end
  94. print("RS Bridge initialized.")
  95.  
  96. -- Initialize Colony Integrator
  97. local colony = peripheral.find("colonyIntegrator")
  98. if not colony then error("Colony Integrator not found.") end
  99. if not colony.isInColony then error("Colony Integrator is not in a colony.") end
  100. print("Colony Integrator initialized.")
  101.  
  102. -- Point to location of chest or storage container
  103. -- A future update may autodetect where the storage container is and error
  104. -- out if no storage container is found.
  105. local storage = "left"
  106. if storage == nil then
  107.   error("No storage container found.")
  108. else
  109.   print("Storage initialized.")
  110. end
  111.  
  112.  
  113. -- Name of log file to capture JSON data from the open requests.  The log can
  114. -- be too big to edit within CC, which may require a "pastebin put" if you want
  115. -- to look at it.  Logging could be improved to only capture Skipped items,
  116. -- which in turn will make log files smaller and edittable in CC directly.
  117. local logFile = "RSWarehouse.log"
  118.  
  119. ----------------------------------------------------------------------------
  120. -- FUNCTIONS
  121. ----------------------------------------------------------------------------
  122.  
  123. -- Prints to the screen one row after another, scrolling the screen when
  124. -- reaching the bottom. Acts as a normal display where text is printed in
  125. -- a standard way. Long lines are not wrapped and newlines are printed as
  126. -- spaces, both to be addressed in a future update.
  127. -- NOTE: No longer used in this program.
  128. function mPrintScrollable(mon, ...)
  129.     w, h = mon.getSize()
  130.     x, y = mon.getCursorPos()
  131.  
  132.     -- Blink the cursor like a normal display.
  133.     mon.setCursorBlink(true)
  134.  
  135.     -- For multiple strings, append them with a space between each.
  136.     for i = 2, #arg do t = t.." "..arg[i] end
  137.     mon.write(arg[1])
  138.     if y >= h then
  139.         mon.scroll(1)
  140.         mon.setCursorPos(1, y)
  141.     else
  142.         mon.setCursorPos(1, y+1)
  143.     end
  144. end
  145.  
  146. -- Prints strings left, centered, or right justified at a specific row and
  147. -- specific foreground/background color.
  148. function mPrintRowJustified(mon, y, pos, text, ...)
  149.     w, h = mon.getSize()
  150.     fg = mon.getTextColor()
  151.     bg = mon.getBackgroundColor()
  152.  
  153.     if pos == "left" then x = 1 end
  154.     if pos == "center" then x = math.floor((w - #text) / 2) end
  155.     if pos == "right" then x = w - #text end
  156.  
  157.     if #arg > 0 then mon.setTextColor(arg[1]) end
  158.     if #arg > 1 then mon.setBackgroundColor(arg[2]) end
  159.     mon.setCursorPos(x, y)
  160.     mon.write(text)
  161.     mon.setTextColor(fg)
  162.     mon.setBackgroundColor(bg)
  163. end
  164.  
  165. -- Utility function that returns true if the provided character is a digit.
  166. -- Yes, this is a hack and there are better ways to do this.  Clearly.
  167. function isdigit(c)
  168.     return c >= "0" and c <= "9"
  169. end
  170.  
  171. -- Utility function that displays current time and remaining time on timer.
  172. -- For time of day, yellow is day, orange is sunset/sunrise, and red is night.
  173. -- The countdown timer is orange over 15s, yellow under 15s, and red under 5s.
  174. -- At night, the countdown timer is red and shows PAUSED insted of a time.
  175. function displayTimer(mon, t)
  176.     now = os.time()
  177.  
  178.     cycle = "day"
  179.     cycle_color = colors.orange
  180.     if now >= 4 and now < 6 then
  181.         cycle = "sunrise"
  182.         cycle_color = colors.orange
  183.     elseif now >= 6 and now < 18 then
  184.         cycle = "day"
  185.         cycle_color = colors.yellow
  186.     elseif now >= 18 and now < 19.5 then
  187.         cycle = "sunset"
  188.         cycle_color = colors.orange
  189.     elseif now >= 19.5 or now < 5 then
  190.         cycle = "night"
  191.         cycle_color = colors.red
  192.     end
  193.  
  194.     timer_color = colors.orange
  195.     if t < 15 then timer_color = colors.yellow end
  196.     if t < 5 then timer_color = colors.red end
  197.  
  198.     mPrintRowJustified(mon, 1, "left", string.format("Time: %s [%s]    ", textutils.formatTime(now, false), cycle), cycle_color)
  199.     if cycle ~= "night" then mPrintRowJustified(mon, 1, "right", string.format("    Remaining: %ss", t), timer_color)
  200.     else mPrintRowJustified(mon, 1, "right", "    Remaining: PAUSED", colors.red) end
  201. end
  202.  
  203. -- Scan all open work requests from the Warehouse and attempt to satisfy those
  204. -- requests.  Display all activity on the monitor, including time of day and the
  205. -- countdown timer before next scan.  This function is not called at night to
  206. -- save on some ticks, as the colonists are in bed anyways.  Items in red mean
  207. -- work order can't be satisfied by Refined Storage (lack of pattern or lack of
  208. -- required crafting ingredients).  Yellow means order partially filled and a
  209. -- crafting job was scheduled for the rest.  Green means order fully filled.
  210. -- Blue means the Player needs to manually fill the work order.  This includes
  211. -- equipment (Tools of Class), NBT items like armor, weapons and tools, as well
  212. -- as generic requests ike Compostables, Fuel, Food, Flowers, etc.
  213. function scanWorkRequests(mon, rs, chest)
  214.     -- Before we do anything, prep the log file for this scan.
  215.     -- The log file is truncated each time this function is called.
  216.     file = fs.open(logFile, "w")
  217.     print("\nScan starting at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").")
  218.  
  219.     -- We want to keep three different lists so that they can be
  220.     -- displayed on the monitor in a more intelligent way.  The first
  221.     -- list is for the Builder requests.  The second list is for the
  222.     -- non-Builder requests.  The third list is for any armor, tools
  223.     -- and weapons requested by the colonists.
  224.     builder_list = {}
  225.     nonbuilder_list = {}
  226.     equipment_list = {}
  227.  
  228.     -- Scan RS for all items in its network. Ignore items with NBT data.
  229.     -- If a Builder needs any items with NBT data, this function will need
  230.     -- to be updated to not ignore those items.
  231.     items = rs.listItems()
  232.     item_array = {}
  233.     for index, item in ipairs(items) do
  234.         if not item.nbt then
  235.             item_array[item.name] = item.amount
  236.         end
  237.     end
  238.  
  239.     -- Scan the Warehouse for all open work requests. For each item, try to
  240.     -- provide as much as possible from RS, then craft whatever is needed
  241.     -- after that. Green means item was provided entirely. Yellow means item
  242.     -- is being crafted. Red means item is missing crafting recipe.
  243.     workRequests = colony.getRequests()
  244.     --file.write(textutils.serialize(workRequests, {allow_repetitions = true}))
  245.     for w in pairs(workRequests) do
  246.         name = workRequests[w].name
  247.         item = workRequests[w].items[1].name
  248.         target = workRequests[w].target
  249.         desc = workRequests[w].desc
  250.         needed = workRequests[w].count
  251.         provided = 0
  252.  
  253.         target_words = {}
  254.         target_length = 0
  255.         for word in target:gmatch("%S+") do
  256.             table.insert(target_words, word)
  257.             target_length = target_length + 1
  258.         end
  259.  
  260.         if target_length >= 3 then target_name = target_words[target_length-2] .. " " .. target_words[target_length]
  261.         else target_name = target end
  262.  
  263.         target_type = ""
  264.         target_count = 1
  265.         repeat
  266.             if target_type ~= "" then target_type = target_type .. " " end
  267.             target_type = target_type .. target_words[target_count]
  268.             target_count = target_count + 1
  269.         until target_count > target_length - 3
  270.  
  271.         useRS = 1
  272.         if string.find(desc, "Tool of class") then useRS = 0 end
  273.         if string.find(name, "Hoe") then useRS = 0 end
  274.         if string.find(name, "Shovel") then useRS = 0 end
  275.         if string.find(name, "Axe") then useRS = 0 end
  276.         if string.find(name, "Pickaxe") then useRS = 0 end
  277.         if string.find(name, "Bow") then useRS = 0 end
  278.         if string.find(name, "Sword") then useRS = 0 end
  279.         if string.find(name, "Shield") then useRS = 0 end
  280.         if string.find(name, "Helmet") then useRS = 0 end
  281.         if string.find(name, "Leather Cap") then useRS = 0 end
  282.         if string.find(name, "Chestplate") then useRS = 0 end
  283.         if string.find(name, "Tunic") then useRS = 0 end
  284.         if string.find(name, "Pants") then useRS = 0 end
  285.         if string.find(name, "Leggings") then useRS = 0 end
  286.         if string.find(name, "Boots") then useRS = 0 end
  287.         if name == "Rallying Banner" then useRS = 0 end --bugged in alpha versions
  288.         if name == "Crafter" then useRS = 0 end
  289.         if name == "Compostable" then useRS = 0 end
  290.         if name == "Fertilizer" then useRS = 0 end
  291.         if name == "Flowers" then useRS = 0 end
  292.         if name == "Food" then useRS = 0 end
  293.         if name == "Fuel" then useRS = 0 end
  294.         if name == "Smeltable Ore" then useRS = 0 end
  295.         if name == "Stack List" then useRS = 0 end
  296.  
  297.         color = colors.blue
  298.         if useRS == 1 then
  299.             if item_array[item] then
  300.                 provided = rs.exportItemToPeripheral({name=item, count=needed}, chest)
  301.             end
  302.  
  303.             color = colors.green
  304.             if provided < needed then
  305.                 if rs.isItemCrafting(item) then
  306.                     color = colors.yellow
  307.                     print("[Crafting]", item)
  308.                 else
  309.                     if rs.craftItem({name=item, count=needed}) then
  310.                         color = colors.yellow
  311.                         print("[Scheduled]", needed, "x", item)
  312.                     else
  313.                         color = colors.red
  314.                         print("[Failed]", item)
  315.                     end
  316.                 end
  317.             end
  318.         else
  319.             nameString = name .. " [" .. target .. "]"
  320.             print("[Skipped]", nameString)
  321.         end
  322.  
  323.         if string.find(desc, "of class") then
  324.             level = "Any Level"
  325.             if string.find(desc, "with maximal level:Leather") then level = "Leather" end
  326.             if string.find(desc, "with maximal level:Gold") then level = "Gold" end
  327.             if string.find(desc, "with maximal level:Chain") then level = "Chain" end
  328.             if string.find(desc, "with maximal level:Wood or Gold") then level = "Wood or Gold" end
  329.             if string.find(desc, "with maximal level:Stone") then level = "Stone" end
  330.             if string.find(desc, "with maximal level:Iron") then level = "Iron" end
  331.             if string.find(desc, "with maximal level:Diamond") then level = "Diamond" end
  332.             new_name = level .. " " .. name
  333.             if level == "Any Level" then new_name = name .. " of any level" end
  334.             new_target = target_type .. " " .. target_name
  335.             equipment = { name=new_name, target=new_target, needed=needed, provided=provided, color=color}
  336.             table.insert(equipment_list, equipment)
  337.         elseif string.find(target, "Builder") then
  338.             builder = { name=name, item=item, target=target_name, needed=needed, provided=provided, color=color }
  339.             table.insert(builder_list, builder)
  340.         else
  341.             new_target = target_type .. " " .. target_name
  342.             if target_length < 3 then
  343.                 new_target = target
  344.             end
  345.             nonbuilder = { name=name, target=new_target, needed=needed, provided=provided, color=color }
  346.             table.insert(nonbuilder_list, nonbuilder)
  347.         end
  348.     end
  349.  
  350.     -- Show the various lists on the attached monitor.
  351.     row = 3
  352.     mon.clear()
  353.  
  354.     header_shown = 0
  355.     for e in pairs(equipment_list) do
  356.         equipment = equipment_list[e]
  357.         if header_shown == 0 then
  358.             mPrintRowJustified(mon, row, "center", "Equipment")
  359.             header_shown = 1
  360.             row = row + 1
  361.         end
  362.         text = string.format("%d %s", equipment.needed, equipment.name)
  363.         mPrintRowJustified(mon, row, "left", text, equipment.color)
  364.         mPrintRowJustified(mon, row, "right", " " .. equipment.target, equipment.color)
  365.         row = row + 1
  366.     end
  367.  
  368.     header_shown = 0
  369.     for b in pairs(builder_list) do
  370.         builder = builder_list[b]
  371.         if header_shown == 0 then
  372.             if row > 1 then row = row + 1 end
  373.             mPrintRowJustified(mon, row, "center", "Builder Requests")
  374.             header_shown = 1
  375.             row = row + 1
  376.         end
  377.         text = string.format("%d/%s", builder.provided, builder.name)
  378.         mPrintRowJustified(mon, row, "left", text, builder.color)
  379.         mPrintRowJustified(mon, row, "right", " " .. builder.target, builder.color)
  380.         row = row + 1
  381.     end
  382.  
  383.     header_shown = 0
  384.     for n in pairs(nonbuilder_list) do
  385.         nonbuilder = nonbuilder_list[n]
  386.         if header_shown == 0 then
  387.             if row > 1 then row = row + 1 end
  388.             mPrintRowJustified(mon, row, "center", "Nonbuilder Requests")
  389.             header_shown = 1
  390.             row = row + 1
  391.         end
  392.         text = string.format("%d %s", nonbuilder.needed, nonbuilder.name)
  393.         if isdigit(nonbuilder.name:sub(1,1)) then
  394.             text = string.format("%d/%s", nonbuilder.provided, nonbuilder.name)
  395.         end
  396.         mPrintRowJustified(mon, row, "left", text, nonbuilder.color)
  397.         mPrintRowJustified(mon, row, "right", " " .. nonbuilder.target, nonbuilder.color)
  398.         row = row + 1
  399.     end
  400.  
  401.     if row == 3 then mPrintRowJustified(mon, row, "center", "No Open Requests") end
  402.     print("Scan completed at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").")
  403.     file.close()
  404. end
  405.  
  406. -- Utility function that displays the building status' for each building within the colony
  407. function displayBuildingInformation(monitor)
  408.     local buildings = colony.getBuildings()
  409.  
  410.     -- Clear the monitor and set the cursor position
  411.     monitor.clear()
  412.     monitor.setCursorPos(1, 1)
  413.  
  414.     -- Display the header "Building Status" in the center of the monitor
  415.     local width, height = monitor.getSize()
  416.     local header = "Building Status"
  417.     local headerStart = math.floor(width / 2 - string.len(header) / 2 + 1)
  418.     monitor.setCursorPos(headerStart, 1)
  419.     monitor.write(header)
  420.  
  421.     --Create separate tables for each building in the list based on it's status
  422.     local workingOn = {}
  423.     local maxLevel = {}
  424.     local notMaxed = {}
  425.     for i, building in ipairs(buildings) do              
  426.         if building.isWorkingOn then
  427.             table.insert(workingOn, building)
  428.         elseif building.level == building.maxLevel then
  429.             table.insert(maxLevel, building)
  430.         else
  431.             table.insert(notMaxed, building)
  432.         end
  433.     end
  434.        
  435.     --Sort the "other buildings" list by level
  436.     table.sort(notMaxed, function(a,b)
  437.         return a.level < b.level
  438.     end)
  439.  
  440.     -- Display the workingOn building information
  441.     local y = 2
  442.     for i, building in ipairs(workingOn) do
  443.         local color = monitor.getTextColor()
  444.         if building.isWorkingOn and building.maxLevel == building.level then
  445.             monitor.setTextColor(colors.red)
  446.         elseif building.isWorkingOn then
  447.             monitor.setTextColor(colors.yellow)
  448.         end
  449.  
  450.         local temp = string.gsub(building.name, "com.minecolonies.building.", "")
  451.         local line = string.upper(string.sub(temp, 1, 1)) .. string.sub(temp, 2)
  452.         local level = building.level
  453.         if building.isWorkingOn and building.level ~= building.maxLevel then
  454.             level = building.level .. " --> " .. (building.level + 1)
  455.         elseif building.isWorkingOn and building.level == building.maxLevel then
  456.             level = "Repairing"
  457.         end
  458.  
  459.         local lineStart = 1
  460.         local levelStart = width - string.len(tostring(level)) + 1
  461.         monitor.setCursorPos(lineStart, y)
  462.         monitor.write(line)
  463.         monitor.setCursorPos(levelStart, y)
  464.         monitor.write(level)
  465.         monitor.setTextColor(color)
  466.         y = y + 1
  467.     end
  468.  
  469.     for i, building in ipairs(notMaxed) do
  470.         local color = monitor.getTextColor()
  471.         if building.level == 0 then
  472.             monitor.setTextColor(colors.lime)
  473.         elseif building.level == 1 then
  474.             monitor.setTextColor(colors.lightGray)
  475.         elseif building.level == 2 then
  476.             monitor.setTextColor(colors.white)
  477.         elseif building.level == 3 then
  478.             monitor.setTextColor(colors.lightBlue)
  479.         elseif building.level == 4 then
  480.             monitor.setTextColor(colors.cyan)
  481.         end
  482.  
  483.  
  484.         local temp = string.gsub(building.name, "com.minecolonies.building.", "")
  485.         local line = string.upper(string.sub(temp, 1, 1)) .. string.sub(temp, 2)
  486.         local level = building.level
  487.         local lineStart = 1
  488.         local levelStart = width - string.len(tostring(level)) + 1
  489.         monitor.setCursorPos(lineStart, y)
  490.         monitor.write(line)
  491.         monitor.setCursorPos(levelStart, y)
  492.         monitor.write(level)
  493.         y = y + 1
  494.     end
  495.  
  496.     for i, building in ipairs(maxLevel) do
  497.       if building.name ~= "com.minecolonies.building.stash" then  
  498.         local color = monitor.getTextColor()
  499.         monitor.setTextColor(colors.blue)
  500.         local temp = string.gsub(building.name, "com.minecolonies.building.", "")
  501.         local line = string.upper(string.sub(temp, 1, 1)) .. string.sub(temp, 2)
  502.         local level = building.level
  503.         local lineStart = 1
  504.         local levelStart = width - string.len(tostring(level)) + 1
  505.         monitor.setCursorPos(lineStart, y)
  506.         monitor.write(line)
  507.         monitor.setCursorPos(levelStart, y)
  508.         monitor.write(level)
  509.         y = y + 1
  510.       end
  511.     end
  512. end
  513.  
  514. ----------------------------------------------------------------------------
  515. -- MAIN
  516. ----------------------------------------------------------------------------
  517.  
  518. -- Scan for requests periodically. This will catch any updates that were
  519. -- triggered from the previous scan. Right-clicking on the monitor will
  520. -- trigger an immediate scan and reset the timer. Unfortunately, there is
  521. -- no way to capture left-clicks on the monitor.
  522. local time_between_runs = 10
  523. local current_run = time_between_runs
  524. scanWorkRequests(selectedMonitor, bridge, storage)
  525. displayTimer(selectedMonitor, current_run)
  526. displayBuildingInformation(otherMonitor)
  527. local TIMER = os.startTimer(1)
  528.  
  529. while true do
  530.     local e = {os.pullEvent()}
  531.     if e[1] == "timer" and e[2] == TIMER then
  532.         now = os.time()
  533.         if now >= 5 and now < 19.5 then
  534.             current_run = current_run - 1
  535.             if current_run <= 0 then
  536.                 scanWorkRequests(selectedMonitor, bridge, storage)
  537.                 displayBuildingInformation(otherMonitor)
  538.                 current_run = time_between_runs
  539.             end
  540.         end
  541.         displayTimer(selectedMonitor, current_run)
  542.         TIMER = os.startTimer(1)
  543.     elseif e[1] == "monitor_touch" then
  544.         os.cancelTimer(TIMER)
  545.         scanWorkRequests(selectedMonitor, bridge, storage)
  546.         displayBuildingInformation(otherMonitor)
  547.         current_run = time_between_runs
  548.         displayTimer(selectedMonitor, current_run)
  549.         TIMER = os.startTimer(1)
  550.     end
  551. end
  552.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement