SHOW:
|
|
- or go back to the newest paste.
1 | -- RSWarehouse.lua | |
2 | -- Author: Chuck Burgess | |
3 | -- Updated: 2024-01-15 | |
4 | ||
5 | local logFile = "RSWarehouse.log" | |
6 | local time_between_runs = 30 | |
7 | ||
8 | -- Initialize Monitor | |
9 | -- see: https://www.computercraft.info/wiki/Advanced_Monitor | |
10 | local monitor = peripheral.find("monitor") | |
11 | if not monitor then error("Monitor not found.") end | |
12 | monitor.setTextScale(0.5) | |
13 | monitor.clear() | |
14 | monitor.setCursorPos(1, 1) | |
15 | monitor.setCursorBlink(false) | |
16 | print("Monitor initialized.") | |
17 | ||
18 | -- Initialize RS Bridge | |
19 | -- see: https://advancedperipherals.madefor.cc/peripherals/rs_bridge/ | |
20 | - | local bridge = peripheral.find("rsBridge") |
20 | + | local bridge = peripheral.find("meBridge") |
21 | - | if not bridge then error("RS Bridge not found.") end |
21 | + | if not bridge then error("ME Bridge not found.") end |
22 | - | print("RS Bridge initialized.") |
22 | + | print("ME Bridge initialized.") |
23 | ||
24 | -- Initialize Colony Integrator | |
25 | -- see: https://docs.advanced-peripherals.de/peripherals/colony_integrator/ | |
26 | local colony = peripheral.find("colonyIntegrator") | |
27 | if not colony then error("Colony Integrator not found.") end | |
28 | if not colony.isInColony then error("Colony Integrator is not in a colony.") end | |
29 | print("Colony Integrator initialized.") | |
30 | ||
31 | -- Establish the direction to transport the items into the Warehouse based on | |
32 | -- where the entnglement block is sitting. Default to empty string. | |
33 | local storage = peripheral.find("entangled:tile") | |
34 | if not storage then error("Warehouse storage not found.") end | |
35 | local direction = "" | |
36 | local names = peripheral.getNames() | |
37 | for _, pos in ipairs(names) do | |
38 | if peripheral.getType(pos) == "entangled:tile" then | |
39 | direction = pos | |
40 | end | |
41 | end | |
42 | print("Warehouse storage initialized.") | |
43 | ||
44 | ---------------------------------------------------------------------------- | |
45 | -- FUNCTIONS | |
46 | ---------------------------------------------------------------------------- | |
47 | --[[ | |
48 | Table.Empty | |
49 | @desc check to see if a table contains any data | |
50 | @return boolean | |
51 | ]] | |
52 | function table.empty (self) | |
53 | for _, _ in pairs(self) do | |
54 | return false | |
55 | end | |
56 | return true | |
57 | end | |
58 | ||
59 | --[[ | |
60 | Write To Log | |
61 | @desc Write the specified `table` to the file surrounded by the `blockTop` and `blockBottom` | |
62 | @return void | |
63 | ]] | |
64 | function writeToLog(data, blockTop, blockBottom) | |
65 | file.write("\n") | |
66 | file.write(blockTop) | |
67 | file.write("\n") | |
68 | file.write(textutils.serialize(data, { allow_repetitions = true })) | |
69 | file.write("\n") | |
70 | file.write(blockBottom) | |
71 | file.write("\n") | |
72 | end | |
73 | ||
74 | --[[ | |
75 | Process Work Request Item | |
76 | @desc Determine if this item can be delivered to the warehouse from the storage | |
77 | @return boolean | |
78 | ]] | |
79 | function processWorkRequestItem(request) | |
80 | if string.find(request.desc, "Tool of class") then return false end | |
81 | if string.find(request.name, "Hoe") then return false end | |
82 | if string.find(request.name, "Shovel") then return false end | |
83 | if string.find(request.name, "Axe") then return false end | |
84 | if string.find(request.name, "Pickaxe") then return false end | |
85 | if string.find(request.name, "Bow") then return false end | |
86 | if string.find(request.name, "Sword") then return false end | |
87 | if string.find(request.name, "Shield") then return false end | |
88 | if string.find(request.name, "Helmet") then return false end | |
89 | if string.find(request.name, "Leather Cap") then return false end | |
90 | if string.find(request.name, "Chestplate") then return false end | |
91 | if string.find(request.name, "Tunic") then return false end | |
92 | if string.find(request.name, "Pants") then return false end | |
93 | if string.find(request.name, "Leggings") then return false end | |
94 | if string.find(request.name, "Boots") then return false end | |
95 | if request.name == "Rallying Banner" then return false end --bugged in alpha versions | |
96 | if request.name == "Crafter" then return false end | |
97 | if request.name == "Compostable" then return false end | |
98 | if request.name == "Fertilizer" then return false end | |
99 | if request.name == "Flowers" then return false end | |
100 | if request.name == "Food" then return false end | |
101 | if request.name == "Fuel" then return false end | |
102 | if request.name == "Smeltable Ore" then return false end | |
103 | if request.name == "Stack List" then return false end | |
104 | -- you can add any new items here if they are found | |
105 | return true | |
106 | end | |
107 | ||
108 | --[[ | |
109 | Monitor Print Row Justified | |
110 | @desc Print a line of data to the in-game monitor | |
111 | @return void | |
112 | ]] | |
113 | function mPrintRowJustified(mon, y, pos, text, textcolor) | |
114 | w, h = mon.getSize() | |
115 | fg = colors.white | |
116 | bg = colors.black | |
117 | ||
118 | if pos == "left" then x = 1 end | |
119 | if pos == "center" then x = math.floor((w - #text) / 2) end | |
120 | if pos == "right" then x = w - #text end | |
121 | ||
122 | mon.setTextColor(textcolor) | |
123 | mon.setCursorPos(x, y) | |
124 | mon.write(text) | |
125 | mon.setTextColor(fg) | |
126 | mon.setBackgroundColor(bg) | |
127 | end | |
128 | ||
129 | --[[ | |
130 | Display Timer | |
131 | @desc Update the time on the monitor | |
132 | @return void | |
133 | ]] | |
134 | function displayTimer(mon, t) | |
135 | now = os.time() | |
136 | cycle = "day" | |
137 | cycle_color = colors.orange | |
138 | if now >= 4 and now < 6 then | |
139 | cycle = "sunrise" | |
140 | cycle_color = colors.yellow | |
141 | elseif now >= 6 and now < 18 then | |
142 | cycle = "day" | |
143 | cycle_color = colors.lightBlue | |
144 | elseif now >= 18 and now < 19.5 then | |
145 | cycle = "sunset" | |
146 | cycle_color = colors.magenta | |
147 | elseif now >= 19.5 or now < 5 then | |
148 | cycle = "night" | |
149 | cycle_color = colors.red | |
150 | end | |
151 | ||
152 | timer_color = colors.green | |
153 | if t < 15 then timer_color = colors.yellow end | |
154 | if t < 5 then timer_color = colors.orange end | |
155 | ||
156 | mPrintRowJustified(mon, 1, "left", string.format("Time: %s [%s] ", textutils.formatTime(now, false), cycle), cycle_color) | |
157 | if cycle ~= "night" then | |
158 | mPrintRowJustified(mon, 1, "right", string.format(" Remaining: %ss", t), timer_color) | |
159 | else | |
160 | mPrintRowJustified(mon, 1, "right", " Remaining: PAUSED", colors.red) | |
161 | end | |
162 | end | |
163 | ||
164 | --[[ | |
165 | Create Colonist Data | |
166 | @desc Build a table of Colonist making the request | |
167 | @return table | |
168 | ]] | |
169 | function createColonistData(colonist) | |
170 | title_words = {} | |
171 | words_in_name = 0 | |
172 | colonist_job = "" | |
173 | word_count = 1 | |
174 | ||
175 | for word in colonist:gmatch("%S+") do | |
176 | table.insert(title_words, word) | |
177 | words_in_name = words_in_name + 1 | |
178 | end | |
179 | ||
180 | if words_in_name >= 3 then colonist_name = title_words[words_in_name-2] .. " " .. title_words[words_in_name] | |
181 | else colonist_name = colonist end | |
182 | ||
183 | repeat | |
184 | if colonist_job ~= "" then colonist_job = colonist_job .. " " end | |
185 | colonist_job = colonist_job .. title_words[word_count] | |
186 | word_count = word_count + 1 | |
187 | until word_count > words_in_name - 3 | |
188 | ||
189 | return { fullName = colonist, titleWords = title_words, job = colonist_job, name = colonist_name, wordsInName = words_in_name } | |
190 | end | |
191 | ||
192 | --[[ | |
193 | Get Work Request List (from colony) | |
194 | @desc Build a table of the work request data from the colony | |
195 | @return table | |
196 | ]] | |
197 | function getWorkRequestList(colony) | |
198 | requestList = {} | |
199 | workRequests = colony.getRequests() | |
200 | file = fs.open(logFile, "w") | |
201 | ||
202 | for w in pairs(workRequests) do | |
203 | writeToLog(workRequests[w], "--- Request start ---", "--- Request end ---"); | |
204 | name = workRequests[w].name -- the name of the count/item being requested | |
205 | colonist = createColonistData(workRequests[w].target) | |
206 | desc = workRequests[w].desc -- the request description | |
207 | item = {} | |
208 | -- create the filter item for the transfer request through the bridge | |
209 | if workRequests[w].items and workRequests[w].items[1] then | |
210 | if not workRequests[w].items[1].nbt or table.empty(workRequests[w].items[1].nbt) then | |
211 | item = { name = workRequests[w].items[1].name, count = workRequests[w].count, displayName = workRequests[w].items[1].displayName} | |
212 | else | |
213 | item = { name = workRequests[w].items[1].name, count = workRequests[w].count, displayName = workRequests[w].items[1].displayName, nbt = workRequests[w].items[1].nbt} | |
214 | end | |
215 | end | |
216 | -- how many items are needed to fulfill this request? | |
217 | needed = workRequests[w].count | |
218 | ||
219 | local newRecord = {} | |
220 | newRecord.name = name | |
221 | newRecord.desc = desc | |
222 | newRecord.needed = needed | |
223 | newRecord.item = item | |
224 | newRecord.colonist = colonist | |
225 | table.insert(requestList, newRecord) | |
226 | writeToLog(newRecord, "--- Record start ---", "--- Record end ---"); | |
227 | end | |
228 | file.close() | |
229 | return requestList | |
230 | end | |
231 | ||
232 | --[[ | |
233 | Display List | |
234 | @desc Update the monitor with the work request items currently in the system | |
235 | @return void | |
236 | ]] | |
237 | function displayList(mon, listName, itemList) | |
238 | -- show the list header first | |
239 | mPrintRowJustified(mon, row, "center", listName, colors.white) | |
240 | row = row + 1 | |
241 | for e in pairs(itemList) do | |
242 | record = itemList[e] | |
243 | text = string.format("%d %s", record.provided , record.name) | |
244 | mPrintRowJustified(mon, row, "left", text, record.color) | |
245 | mPrintRowJustified(mon, row, "right", " " .. record.colonist, record.color) | |
246 | row = row + 1 | |
247 | end | |
248 | -- add a space at the end of the list | |
249 | row = row + 1 | |
250 | end | |
251 | ||
252 | -- Color References: | |
253 | -- RED: work order can't be satisfied by Refined Storage (lack of pattern or lack of | |
254 | -- required crafting ingredients). | |
255 | -- YELLOW: order partially filled and a crafting job was scheduled for the rest. | |
256 | -- GREEN: order fully filled. | |
257 | -- BLUE: the Player needs to manually fill the work order. This includes some equipment as well as generic requests ike Compostables, Fuel, Food, Flowers, etc. | |
258 | --[[ | |
259 | Scan Work Requests | |
260 | @desc Manages all of the open work requests in the system and attempts to fulfill them from the inventory | |
261 | @desc Not called at night (as determined by the server) since requests cannot be fulfilled anyway | |
262 | @return void | |
263 | ]] | |
264 | function scanWorkRequests(mon, bridge, direction) | |
265 | ||
266 | print("\nScan starting at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").") | |
267 | builder_list = {} | |
268 | nonbuilder_list = {} | |
269 | equipment_list = {} | |
270 | requestList = getWorkRequestList(colony) | |
271 | ||
272 | for j, data in ipairs(requestList) do | |
273 | color = colors.blue | |
274 | provided = 0 | |
275 | ||
276 | if processWorkRequestItem(data) then | |
277 | provided = bridge.exportItemToPeripheral(data.item, direction) | |
278 | color = colors.lightGray | |
279 | if provided >= data.needed then | |
280 | color = colors.green | |
281 | end | |
282 | -- only handle the Non-NBT data items or empty nbt table item records | |
283 | if provided < data.needed then | |
284 | if bridge.isItemCrafting(data.item) then | |
285 | color = colors.yellow | |
286 | print("[Crafting]", data.name) | |
287 | else | |
288 | if bridge.craftItem(data.item) then | |
289 | color = colors.yellow | |
290 | print("[Scheduled]", data.item.count, "x", data.name) | |
291 | else | |
292 | color = colors.red | |
293 | print("[Failed]", data.name) | |
294 | end | |
295 | end | |
296 | end | |
297 | else | |
298 | nameString = data.name .. " [" .. data.colonist.fullName .. "]" | |
299 | print("[Skipped]", nameString) | |
300 | end | |
301 | -- --------------------------------------------------------------------- | |
302 | -- Build the newList data | |
303 | -- --------------------------------------------------------------------- | |
304 | -- create the target text | |
305 | expectedList = "Builder" | |
306 | colonist = data.colonist.name | |
307 | if not string.find(data.colonist.fullName, "Builder") then | |
308 | expectedList = "" | |
309 | colonist = data.colonist.job .. " " .. data.colonist.name | |
310 | if data.colonist.wordsInName < 3 then | |
311 | colonist = data.colonist.name | |
312 | end | |
313 | end | |
314 | ||
315 | -- create the name | |
316 | listName = data.name | |
317 | if string.find(data.desc, "level") then | |
318 | expectedList = "Equipment" | |
319 | level = "Any Level" | |
320 | if string.find(data.desc, "with maximal level: Leather") then level = "Leather" end | |
321 | if string.find(data.desc, "with maximal level: Gold") then level = "Gold" end | |
322 | if string.find(data.desc, "with maximal level: Chain") then level = "Chain" end | |
323 | if string.find(data.desc, "with maximal level: Wood or Gold") then level = "Wood or Gold" end | |
324 | if string.find(data.desc, "with maximal level: Stone") then level = "Stone" end | |
325 | if string.find(data.desc, "with maximal level: Iron") then level = "Iron" end | |
326 | if string.find(data.desc, "with maximal level: Diamond") then level = "Diamond" end | |
327 | listName = level .. " " .. data.name | |
328 | if level == "Any Level" then listName = data.name .. " of any level" end | |
329 | end | |
330 | ||
331 | -- create the new list table defining what is inserted into a specific list | |
332 | newList = { name=listName, colonist=colonist, needed=data.needed, provided=provided, color=color} | |
333 | ||
334 | if expectedList == "Equipment" then | |
335 | table.insert(equipment_list, newList) | |
336 | elseif expectedList == "Builder" then | |
337 | table.insert(builder_list, newList) | |
338 | else | |
339 | table.insert(nonbuilder_list, newList) | |
340 | end | |
341 | -- --------------------------------------------------------------------- | |
342 | end | |
343 | ||
344 | -- Show the various lists on the attached monitor. | |
345 | mon.clear() | |
346 | row = 3 | |
347 | if not table.empty(builder_list) then displayList(mon, "Builder Requests", builder_list) end | |
348 | if not table.empty(nonbuilder_list) then displayList(mon, "Nonbuilder Requests", nonbuilder_list) end | |
349 | if not table.empty(equipment_list) then displayList(mon, "Equipment", equipment_list) end | |
350 | ||
351 | -- no requests | |
352 | if row == 3 then | |
353 | mPrintRowJustified(mon, row, "center", "No Open Requests", colors.white) | |
354 | end | |
355 | print("Scan completed at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").") | |
356 | end | |
357 | ||
358 | ||
359 | --[[ | |
360 | MAIN | |
361 | @desc establish the run times and execute the work request management | |
362 | @return void | |
363 | ]] | |
364 | local current_run = time_between_runs | |
365 | scanWorkRequests(monitor, bridge, direction) | |
366 | displayTimer(monitor, current_run) | |
367 | local TIMER = os.startTimer(1) | |
368 | ||
369 | while true do | |
370 | local e = {os.pullEvent()} | |
371 | if e[1] == "timer" and e[2] == TIMER then | |
372 | now = os.time() | |
373 | if now >= 5 and now < 19.5 then | |
374 | current_run = current_run - 1 | |
375 | if current_run <= 0 then | |
376 | scanWorkRequests(monitor, bridge, direction) | |
377 | current_run = time_between_runs | |
378 | end | |
379 | end | |
380 | displayTimer(monitor, current_run) | |
381 | TIMER = os.startTimer(1) | |
382 | elseif e[1] == "monitor_touch" then | |
383 | os.cancelTimer(TIMER) | |
384 | scanWorkRequests(monitor, bridge, direction) | |
385 | current_run = time_between_runs | |
386 | displayTimer(monitor, current_run) | |
387 | TIMER = os.startTimer(1) | |
388 | end | |
389 | end |