View difference between Paste ID: uxkZ1pCZ and yac8keDW
SHOW: | | - or go back to the newest paste.
1
-- ********************************************************************************** --
2
-- **                                                                              ** --
3
-- **   Minecraft AE2 Auto-Stocker by RandomBlue (E.J. Wilburn)                    ** --
4
-- **   ----------------------------------------------------                       ** --
5
-- **                                                                              ** --
6
-- **   This program automatically crafts items necessary to maintain a minimum    ** --
7
-- **   stock level of specific items.  The items are configured in a file on      ** --
8
-- **   a computercraft computer named stock_list.txt in the stocker directory.    ** --
9
-- **   Examine that file for example formatting and details.                      ** --
10
-- **                                                                              ** --
11
-- **   Minimum stock levels and crafting batch sizes are configurable per item.   ** --
12
-- **                                                                              ** --
13
-- **   The computer must be placed adjacent to a full block ME Interface attached ** --
14
-- **   to an ME Network where both the items are stored and the crafting CPUs are ** --
15
-- **   located.  Each item you wish to maintain a stock level for must have       ** --
16
-- **   autocrafting enabled for it.                                               ** --
17
-- **                                                                              ** --
18
-- **   Arguments                                                                  ** --
19
-- **   ----------------------------------------------------                       ** --
20
-- **   checkFrequency (optional) - How often inventory levels are checked in      ** --
21
-- **                               seconds.                                       ** --
22
-- **   attachSide (optional)     - Side the computer is attached to the           ** --
23
-- **                               ME Interface (full block version).             ** --
24
-- **   stockFileName (optional)  - Full path to the file containing stocking      ** --
25
-- **                               requirements.                                  ** --
26
-- **                                                                              ** --
27
-- **  Change Log:                                                                 ** --
28
-- **    8th Sep 2015:  [v0.1]  Initial Release                                    ** --
29
-- **    11th Sep 2015: [v0.11] Minor bug fix - attempting to crafting 0 items     ** --
30
-- **                           when current quantity equals minQuantity           ** --
31
-- **                                                                              ** --
32
-- **  TODO:                                                                       ** --
33
-- **    1) Save command line parameters to startup script.                        ** --
34
-- **                                                                              ** --
35
-- ********************************************************************************** --
36
37
-- Parameters with default values.
38
local checkFrequency = 15 -- How often inventory levels are checked in seconds.  Overridden by passing as the first argument.
39
local attachSide = "bottom" -- Side the computer is attached to the ME Interface (full block version).
40
                            -- Overridden by passing as the second argument.
41
local stockFileName = "stocker/stock_list.txt" -- Change this if you want the file somewhere else.  Can be
42
                                               -- overridden via a parameter.
43
local recraftDelay = 300 -- Delay, in seconds, before allowing an item to be crafted again.  If them item in question exceeds
44
                         -- its min quantity before the delay expires, the delay is reset as it's assumed the job
45
                         -- completed.  300 seconds = 5 minutes
46
local delayedItems = {} -- List of delayed items by id:variant with delay time in seconds.  Decremented each loop by
47
                        -- checkFrequency ammount.  When the delay hits 0 or lower then the item is removed from
48
                        -- the list.
49
50
local DEBUG = false
51
52
-- Process the input arguments - storing them to global variables
53
local args = { ... }
54
55
function main(args)
56
	processArgs(args)
57
	local ae2 = attachToAe2(attachSide)
58
	local stocks = loadStockFile(stockFileName)
59
	displayStockingInfo(stocks)
60
	enableAutoRestart()
61
62
	while (true) do
63
		print("[" .. getDisplayTime() .. "] Checking inventory.")
64
		updateDelayedItems(delayedItems)
65
		local allItems = getAllItems(ae2)
66
		for i=1, #allItems do
67
			if (allItems[i].is_craftable == true) then
68
				stockItem(allItems[i], stocks, ae2)
69
			end
70
		end
71
		os.sleep(checkFrequency)
72
	end
73
end
74
75
function isValidSide(side)
76
	if (side == "left" or side == "right" or side == "top" or side == "bottom" or side == "front" or side == "back") then
77
		return true
78
	else
79
		return false
80
	end
81
end
82
83
function processArgs(args)
84
	if (#args >= 1) then
85
		assert(type(args[1]) == "number", "The first parameter (checkFrequency) must be a number.")
86
		checkFrequency = args[1]
87
	end
88
89
	if (#args > 1) then
90
		assert(type(args[2]) == "string", "The second parameter (attachSide) must be a string.")
91
		attachSide = args[2]:lower()
92
	end
93
	assert(isValidSide(attachSide),	"The attachSide parameter must be a valid side: left, right, front, back, top, bottom")
94
95
	if (#args > 2) then
96
		assert(type(args[3]) == "string", "The third parameter (stockFileName) must be a string.")
97
		stockFileName = args[3]
98
	end
99
	assert(fs.exists(stockFileName), "The stock file does not exist: " .. stockFileName)
100
end
101
102
function attachToAe2(attachSide)
103
	-- Make sure the attached device is actually an ME Interface.
104
	assert(peripheral.getType(attachSide) == "tileinterface", "The computer must be attached to a full block " ..
105
		"ME Inteface on the specified side.")
106
	return peripheral.wrap(attachSide)
107
end
108
109
function loadStockFile(stockFileName)
110
	local stockFile = fs.open(stockFileName, "r")
111
	local stockFileContents = stockFile.readAll();
112
	stockFile.close();
113
	local outputStocks = textutils.unserialize(stockFileContents)
114
115
	if (DEBUG) then
116
		print("Stock file: ")
117
		print(stockFileContents)
118
		print("Output stocks length: " .. #outputStocks)
119
		print("Output stocks: ")
120
		for i=1, #outputStocks do
121
			print("itemId: " .. outputStocks[i].itemId)
122
			print("variant: " .. outputStocks[i].variant)
123
			print("minQuantity: " .. outputStocks[i].minQuantity)
124
			print("batchSize: " .. outputStocks[i].batchSize)
125
		end
126
	end
127
128
	assert(#outputStocks > 0, "There are no entries in the " .. stockFileName .. " file.")
129
	return outputStocks
130
end
131
132
function displayStockingInfo(stocks)
133
	print("Stocking info:")
134
	for i=1, #stocks do
135
		print(" itemId: " .. stocks[i].itemId .. ":" .. stocks[i].variant .. " minQuantity: " .. stocks[i].minQuantity ..
136
			" batchSize: " .. stocks[i].batchSize)
137
	end
138
end
139
140
function getAllItems(ae2)
141
	local outputAllItems = ae2.getAvailableItems()
142
	assert(outputAllItems ~= nil, "No craftable items found in this AE2 network.")
143
	assert(#outputAllItems > 0, "No craftable items found in this AE2 network.")
144
	return outputAllItems
145
end
146
147
function isCpuAvailable(ae2)
148
	local cpus = ae2.getCraftingCPUs()
149
	for i=1, #cpus do
150
		if (cpus[i].busy == false) then return true end
151
	end
152
	return false
153
end
154
155
function findStockSetting(fingerprint, stocks)
156
	for i=1, #stocks do
157
		if (stocks[i].itemId == fingerprint.id and stocks[i].variant == fingerprint.dmg) then
158
			return stocks[i]
159
		end
160
	end
161
	return nil
162
end
163
164
function stockItem(currItem, stocks, ae2)
165
	local stockSetting = findStockSetting(currItem.fingerprint, stocks)
166
167
	if (stockSetting == nil or currItem.size >= stockSetting.minQuantity or isDelayed(currItem.fingerprint, delayedItems)
168
	    or isCpuAvailable(ae2) == false) then return end
169
170
	local neededAmount = math.ceil((stockSetting.minQuantity - currItem.size) / stockSetting.batchSize) * stockSetting.batchSize
171
172
	ae2.requestCrafting(currItem.fingerprint, neededAmount)
173
	delayItem(currItem.fingerprint, delayedItems)
174
	print("[" .. getDisplayTime() .. "] Item " .. stockSetting.displayName ..
175
		" is below its min stock level of " .. stockSetting.minQuantity .. ".  Crafting " .. neededAmount .. " more.")
176
end
177
178
function getDisplayTime()
179
	return textutils.formatTime(os.time(), false)
180
end
181
182
function delayItem(fingerprint, delayedItems)
183
	local fullItemName = fingerprintToFullName(fingerprint)
184
	
185
	if(delayedItems == nil) then
186
		delayedItems = {}
187
	end
188
189
	for i=1, #delayedItems do
190
		if (delayedItems[i].fullName == fullItemName) then
191
			delayedItems[i].delay = recraftDelay
192
			return
193
		end
194
	end
195
196
	local delayedItem = {fullName = fullItemName, delay = recraftDelay}
197
	delayedItems[#delayedItems+1] = delayedItem
198
end
199
200
function updateDelayedItems(delayedItems)
201
	if (delayedItems == nil or #delayedItems < 1) then return end
202
203
	local removeIndexes = {}
204
	for i=1, #delayedItems do
205
		currItem = delayedItems[i]
206
		currItem.delay = currItem.delay - checkFrequency
207
		if (currItem.delay < 0) then
208
			table.insert(removeIndexes, i)
209
		end
210
	end
211
212
	-- This should remove items from the end of the list towards the beginning
213
	-- so the list being reordered won't matter.
214
	for i=1, #removeIndexes do
215
		table.remove(delayedItems, removeIndexes[i])
216
	end
217
end
218
219
function fingerprintToFullName(fingerprint)
220
	return fingerprint.id .. ":" .. fingerprint.dmg
221
end
222
223
function isDelayed(fingerprint, delayedItems)
224
	if (delayedItems == nil or #delayedItems < 1) then return false end
225
226
	local fullItemName = fingerprintToFullName(fingerprint)
227
	for i=1, #delayedItems do
228
		if (delayedItems[i].fullName == fullItemName and delayedItems[i].delay > 0) then
229
			return true
230
		end
231
	end
232
233
	return false
234
end
235
236
function enableAutoRestart()
237
	-- Skip this if any startup file already exists.
238
	-- Let the user manaully delete or edit the startup file at that point.
239
	-- Notify the user.
240
	if (fs.exists("startup") == true) then
241
		print("Startup file already exists.")
242
		return
243
	end
244
245
	outputFile = fs.open("startup", "w")
246
247
	-- Write an info message so that people know how to get out of auto-resume
248
	outputFile.write("\nprint(\"Running auto-restart...\")\n")
249
	outputFile.write("print(\"If you want to stop auto-resume and restore original state:\")\n")
250
	outputFile.write("print(\"1) Hold Ctrl-T until the program terminates\")\n")
251
	outputFile.write("print(\"2) Type \\\"rm startup\\\" (without quotes) and hit Enter\")\n")
252
	outputFile.write("print(\"\")\n\n")
253
254
	-- Write the code required to restart the turtle
255
	outputFile.write("shell.run(\"")
256
	outputFile.write(shell.getRunningProgram())
257
	outputFile.write("\")\n")
258
	outputFile.close()
259
end
260
261
-- Start the actual program
262
main(args)