PancakePhD

Untitled

Dec 30th, 2022
51
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.76 KB | None | 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, 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 = "left"
  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. w, h = mon.getSize()
  79. x, y = mon.getCursorPos()
  80.  
  81. -- Blink the cursor like a normal display.
  82. mon.setCursorBlink(true)
  83.  
  84. -- For multiple strings, append them with a space between each.
  85. for i = 2, #arg do t = t.." "..arg[i] end
  86. mon.write(arg[1])
  87. if y >= h then
  88. mon.scroll(1)
  89. mon.setCursorPos(1, y)
  90. else
  91. mon.setCursorPos(1, y+1)
  92. end
  93. end
  94.  
  95. -- Prints strings left, centered, or right justified at a specific row and
  96. -- specific foreground/background color.
  97. function mPrintRowJustified(mon, y, pos, text, ...)
  98. w, h = mon.getSize()
  99. fg = mon.getTextColor()
  100. bg = mon.getBackgroundColor()
  101.  
  102. if pos == "left" then x = 1 end
  103. if pos == "center" then x = math.floor((w - #text) / 2) end
  104. if pos == "right" then x = w - #text end
  105.  
  106. if #arg > 0 then mon.setTextColor(arg[1]) end
  107. if #arg > 1 then mon.setBackgroundColor(arg[2]) end
  108. mon.setCursorPos(x, y)
  109. mon.write(text)
  110. mon.setTextColor(fg)
  111. mon.setBackgroundColor(bg)
  112. end
  113.  
  114. -- Utility function that returns true if the provided character is a digit.
  115. -- Yes, this is a hack and there are better ways to do this. Clearly.
  116. function isdigit(c)
  117. if c == "0" then return true end
  118. if c == "1" then return true end
  119. if c == "2" then return true end
  120. if c == "3" then return true end
  121. if c == "4" then return true end
  122. if c == "5" then return true end
  123. if c == "6" then return true end
  124. if c == "7" then return true end
  125. if c == "8" then return true end
  126. if c == "9" then return true end
  127. return false
  128. end
  129.  
  130. -- Utility function that displays current time and remaining time on timer.
  131. -- For time of day, yellow is day, orange is sunset/sunrise, and red is night.
  132. -- The countdown timer is orange over 15s, yellow under 15s, and red under 5s.
  133. -- At night, the countdown timer is red and shows PAUSED insted of a time.
  134. function displayTimer(mon, t)
  135. now = os.time()
  136.  
  137. cycle = "day"
  138. cycle_color = colors.orange
  139. if now >= 4 and now < 6 then
  140. cycle = "sunrise"
  141. cycle_color = colors.orange
  142. elseif now >= 6 and now < 18 then
  143. cycle = "day"
  144. cycle_color = colors.yellow
  145. elseif now >= 18 and now < 19.5 then
  146. cycle = "sunset"
  147. cycle_color = colors.orange
  148. elseif now >= 19.5 or now < 5 then
  149. cycle = "night"
  150. cycle_color = colors.red
  151. end
  152.  
  153. timer_color = colors.orange
  154. if t < 15 then timer_color = colors.yellow end
  155. if t < 5 then timer_color = colors.red end
  156.  
  157. mPrintRowJustified(mon, 1, "left", string.format("Time: %s [%s] ", textutils.formatTime(now, false), cycle), cycle_color)
  158. if cycle ~= "night" then mPrintRowJustified(mon, 1, "right", string.format(" Remaining: %ss", t), timer_color)
  159. else mPrintRowJustified(mon, 1, "right", " Remaining: PAUSED", colors.red) end
  160. end
  161.  
  162. -- Scan all open work requests from the Warehouse and attempt to satisfy those
  163. -- requests. Display all activity on the monitor, including time of day and the
  164. -- countdown timer before next scan. This function is not called at night to
  165. -- save on some ticks, as the colonists are in bed anyways. Items in red mean
  166. -- work order can't be satisfied by Refined Storage (lack of pattern or lack of
  167. -- required crafting ingredients). Yellow means order partially filled and a
  168. -- crafting job was scheduled for the rest. Green means order fully filled.
  169. -- Blue means the Player needs to manually fill the work order. This includes
  170. -- equipment (Tools of Class), NBT items like armor, weapons and tools, as well
  171. -- as generic requests ike Compostables, Fuel, Food, Flowers, etc.
  172. function scanWorkRequests(mon, rs, chest)
  173. -- Before we do anything, prep the log file for this scan.
  174. -- The log file is truncated each time this function is called.
  175. file = fs.open(logFile, "w")
  176. print("\nScan starting at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").")
  177.  
  178. -- We want to keep three different lists so that they can be
  179. -- displayed on the monitor in a more intelligent way. The first
  180. -- list is for the Builder requests. The second list is for the
  181. -- non-Builder requests. The third list is for any armor, tools
  182. -- and weapons requested by the colonists.
  183. builder_list = {}
  184. nonbuilder_list = {}
  185. equipment_list = {}
  186.  
  187. -- Scan RS for all items in its network. Ignore items with NBT data.
  188. -- If a Builder needs any items with NBT data, this function will need
  189. -- to be updated to not ignore those items.
  190. items = rs.listItems()
  191. item_array = {}
  192. for index, item in ipairs(items) do
  193. if not item.nbt then
  194. item_array[item.name] = item.amount
  195. end
  196. end
  197.  
  198. -- Scan the Warehouse for all open work requests. For each item, try to
  199. -- provide as much as possible from RS, then craft whatever is needed
  200. -- after that. Green means item was provided entirely. Yellow means item
  201. -- is being crafted. Red means item is missing crafting recipe.
  202. workRequests = colony.getRequests()
  203. file.write(textutils.serialize(workRequests))
  204. for w in pairs(workRequests) do
  205. name = workRequests[w].name
  206. item = workRequests[w].items[1].name
  207. target = workRequests[w].target
  208. desc = workRequests[w].desc
  209. needed = workRequests[w].count
  210. provided = 0
  211.  
  212. target_words = {}
  213. target_length = 0
  214. for word in target:gmatch("%S+") do
  215. table.insert(target_words, word)
  216. target_length = target_length + 1
  217. end
  218.  
  219. if target_length >= 3 then target_name = target_words[target_length-2] .. " " .. target_words[target_length]
  220. else target_name = target end
  221.  
  222. target_type = ""
  223. target_count = 1
  224. repeat
  225. if target_type ~= "" then target_type = target_type .. " " end
  226. target_type = target_type .. target_words[target_count]
  227. target_count = target_count + 1
  228. until target_count > target_length - 3
  229.  
  230. useRS = 1
  231. if string.find(desc, "Tool of class") then useRS = 0 end
  232. if string.find(name, "Hoe") then useRS = 0 end
  233. if string.find(name, "Shovel") then useRS = 0 end
  234. if string.find(name, "Axe") then useRS = 0 end
  235. if string.find(name, "Pickaxe") then useRS = 0 end
  236. if string.find(name, "Bow") then useRS = 0 end
  237. if string.find(name, "Sword") then useRS = 0 end
  238. if string.find(name, "Shield") then useRS = 0 end
  239. if string.find(name, "Helmet") then useRS = 0 end
  240. if string.find(name, "Leather Cap") then useRS = 0 end
  241. if string.find(name, "Chestplate") then useRS = 0 end
  242. if string.find(name, "Tunic") then useRS = 0 end
  243. if string.find(name, "Pants") then useRS = 0 end
  244. if string.find(name, "Leggings") then useRS = 0 end
  245. if string.find(name, "Boots") then useRS = 0 end
  246. if name == "Rallying Banner" then useRS = 0 end --bugged in alpha versions
  247. if name == "Crafter" then useRS = 0 end
  248. if name == "Compostable" then useRS = 0 end
  249. if name == "Fertilizer" then useRS = 0 end
  250. if name == "Flowers" then useRS = 0 end
  251. if name == "Food" then useRS = 0 end
  252. if name == "Fuel" then useRS = 0 end
  253. if name == "Smeltable Ore" then useRS = 0 end
  254. if name == "Stack List" then useRS = 0 end
  255.  
  256. color = colors.blue
  257. if useRS == 1 then
  258. if item_array[item] then
  259. provided = rs.exportItemToPeripheral({name=item, count=needed}, chest)
  260. end
  261.  
  262. color = colors.green
  263. if provided < needed then
  264. if rs.isItemCrafting(item) then
  265. color = colors.yellow
  266. print("[Crafting]", item)
  267. else
  268. if rs.craftItem({name=item, count=needed}) then
  269. color = colors.yellow
  270. print("[Scheduled]", needed, "x", item)
  271. else
  272. color = colors.red
  273. print("[Failed]", item)
  274. end
  275. end
  276. end
  277. else
  278. nameString = name .. " [" .. target .. "]"
  279. print("[Skipped]", nameString)
  280. end
  281.  
  282. if string.find(desc, "of class") then
  283. level = "Any Level"
  284. if string.find(desc, "with maximal level:Leather") then level = "Leather" end
  285. if string.find(desc, "with maximal level:Gold") then level = "Gold" end
  286. if string.find(desc, "with maximal level:Chain") then level = "Chain" end
  287. if string.find(desc, "with maximal level:Wood or Gold") then level = "Wood or Gold" end
  288. if string.find(desc, "with maximal level:Stone") then level = "Stone" end
  289. if string.find(desc, "with maximal level:Iron") then level = "Iron" end
  290. if string.find(desc, "with maximal level:Diamond") then level = "Diamond" end
  291. new_name = level .. " " .. name
  292. if level == "Any Level" then new_name = name .. " of any level" end
  293. new_target = target_type .. " " .. target_name
  294. equipment = { name=new_name, target=new_target, needed=needed, provided=provided, color=color}
  295. table.insert(equipment_list, equipment)
  296. elseif string.find(target, "Builder") then
  297. builder = { name=name, item=item, target=target_name, needed=needed, provided=provided, color=color }
  298. table.insert(builder_list, builder)
  299. else
  300. new_target = target_type .. " " .. target_name
  301. if target_length < 3 then
  302. new_target = target
  303. end
  304. nonbuilder = { name=name, target=new_target, needed=needed, provided=provided, color=color }
  305. table.insert(nonbuilder_list, nonbuilder)
  306. end
  307. end
  308.  
  309. -- Show the various lists on the attached monitor.
  310. row = 3
  311. mon.clear()
  312.  
  313. header_shown = 0
  314. for e in pairs(equipment_list) do
  315. equipment = equipment_list[e]
  316. if header_shown == 0 then
  317. mPrintRowJustified(mon, row, "center", "Equipment")
  318. header_shown = 1
  319. row = row + 1
  320. end
  321. text = string.format("%d %s", equipment.needed, equipment.name)
  322. mPrintRowJustified(mon, row, "left", text, equipment.color)
  323. mPrintRowJustified(mon, row, "right", " " .. equipment.target, equipment.color)
  324. row = row + 1
  325. end
  326.  
  327. header_shown = 0
  328. for b in pairs(builder_list) do
  329. builder = builder_list[b]
  330. if header_shown == 0 then
  331. if row > 1 then row = row + 1 end
  332. mPrintRowJustified(mon, row, "center", "Builder Requests")
  333. header_shown = 1
  334. row = row + 1
  335. end
  336. text = string.format("%d/%s", builder.provided, builder.name)
  337. mPrintRowJustified(mon, row, "left", text, builder.color)
  338. mPrintRowJustified(mon, row, "right", " " .. builder.target, builder.color)
  339. row = row + 1
  340. end
  341.  
  342. header_shown = 0
  343. for n in pairs(nonbuilder_list) do
  344. nonbuilder = nonbuilder_list[n]
  345. if header_shown == 0 then
  346. if row > 1 then row = row + 1 end
  347. mPrintRowJustified(mon, row, "center", "Nonbuilder Requests")
  348. header_shown = 1
  349. row = row + 1
  350. end
  351. text = string.format("%d %s", nonbuilder.needed, nonbuilder.name)
  352. if isdigit(nonbuilder.name:sub(1,1)) then
  353. text = string.format("%d/%s", nonbuilder.provided, nonbuilder.name)
  354. end
  355. mPrintRowJustified(mon, row, "left", text, nonbuilder.color)
  356. mPrintRowJustified(mon, row, "right", " " .. nonbuilder.target, nonbuilder.color)
  357. row = row + 1
  358. end
  359.  
  360. if row == 3 then mPrintRowJustified(mon, row, "center", "No Open Requests") end
  361. print("Scan completed at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").")
  362. file.close()
  363. end
  364.  
  365. ----------------------------------------------------------------------------
  366. -- MAIN
  367. ----------------------------------------------------------------------------
  368.  
  369. -- Scan for requests periodically. This will catch any updates that were
  370. -- triggered from the previous scan. Right-clicking on the monitor will
  371. -- trigger an immediate scan and reset the timer. Unfortunately, there is
  372. -- no way to capture left-clicks on the monitor.
  373. local time_between_runs = 30
  374. local current_run = time_between_runs
  375. scanWorkRequests(monitor, bridge, storage)
  376. displayTimer(monitor, current_run)
  377. local TIMER = os.startTimer(1)
  378.  
  379. while true do
  380. local e = {os.pullEvent()}
  381. if e[1] == "timer" and e[2] == TIMER then
  382. now = os.time()
  383. if now >= 5 and now < 19.5 then
  384. current_run = current_run - 1
  385. if current_run <= 0 then
  386. scanWorkRequests(monitor, bridge, storage)
  387. current_run = time_between_runs
  388. end
  389. end
  390. displayTimer(monitor, current_run)
  391. TIMER = os.startTimer(1)
  392. elseif e[1] == "monitor_touch" then
  393. os.cancelTimer(TIMER)
  394. scanWorkRequests(monitor, bridge, storage)
  395. current_run = time_between_runs
  396. displayTimer(monitor, current_run)
  397. TIMER = os.startTimer(1)
  398. end
  399. end
Add Comment
Please, Sign In to add comment