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