-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathelevator-main.lua
414 lines (365 loc) · 12.5 KB
/
elevator-main.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
--[[
Elevator Control by Hamish Robertson
Aliases: OminousPenguin, MC username: Peren
Licence: Creative Commons Attribution-NonCommercial-ShareAlike (http://creativecommons.org/licenses/by-nc-sa/3.0/)
This is a LUA program for ComputerCraft, a Minecraft mod by Daniel Ratcliffe aka dan200 (http://www.computercraft.info/)
This program is for controlling elevators from the Railcraft mod (http://www.railcraft.info/)
For help and feedback please use this forum thread: http://www.computercraft.info/forums2/index.php?/topic/1302-railcraft-mod-elevator-control/
This is version 2b. It is in the beta release stage and so there may be bugs. If you believe you have found a bug, please report it at the above forum thread
The following four variables can be used to specify details of the wiring you have used:
--]]
bundleSide = "bottom" -- The side of the computer to which the bundled cable is attached
elevatorWire = "purple" -- Colour of insulated wire from bundled cable to back of elevator track. Purple = 10th color on rednet cable
detetctorWire = "white" -- Colour of insulated wire from bundled cable to cart detector. White = default color on rednet cable
boardingWire = "lime" -- Colour of insulated wire from bundled cable to boarding rail. Lime = 5th color on rednet cable
--[[
Use Ctrl+T to terminate the program.
You do not need to change anything below this point.
You are permitted to modify and or build upon this work.
If you make your own version publicly available, then please also publish it at the above forum thread. Thank you and happy coding!
--]]
version = "2.2.0b"
response = http.get("http://pastebin.com/raw.php?i=r3mt8mDD")
if response then
latestVersion = response.readLine()
response.close()
if latestVersion ~= version then
updateAvailable = true
end
end
CHANNEL_ELEVATOR = 34080
acceptInput, ignoreDetector, newFloorTimer, departedTimer, ignoreUpdates, ignoreUpdateTimer = false, true, nil, nil, false, nil
dir = shell.getRunningProgram(); i = string.find(dir, "/")
if i then
config = dir:sub(1,i).."/elevator.cfg"
else
config = "elevator.cfg"
end
file = assert(io.open(config, "r"), "Failed to open "..config.."\nRun setup.lua first")
modemSide,elevID,coords,floors = file:read("*l"),file:read("*l"),file:read("*l"),file:read("*l"); file:close()
iter = string.gmatch(coords, "([^\031]+)") -- create iterator
x,y,z = tonumber(iter()),tonumber(iter()),tonumber(iter());iter = nil
modem = peripheral.wrap(modemSide)
if z ~= nil then
print("Starting GPS host")
if not modem.isOpen(gps.CHANNEL_GPS) then
print("Opening GPS channel on "..modemSide.." modem")
modem.open(gps.CHANNEL_GPS)
end
else
y = x; x = nil
end
function writeToPos(x, y, str)
term.setCursorPos(x, y)
term.write(str)
end
function updateMenu()
term.clear()
local selected = 1
writeToPos(4, 2, "Current version: "..version)
writeToPos(5, 3, "Latest version: "..latestVersion)
writeToPos(4, 5, "Please select one of the following options:")
writeToPos(6, 7, "[x] Update all elevators within range")
writeToPos(6, 8, " x Update this elevator")
writeToPos(1, 19, "Arrow keys change selection Backspace to go back")
local function changeSelection(previousSelection)
writeToPos(6,6+previousSelection," x ")
writeToPos(6,6+selected,"[x]")
end
local keyHandlers = {
[200] = function () -- up arrow
if selected == 2 then
selected = 1
changeSelection(2)
end
end,
[208] = function () -- down arrow
if selected == 1 then
selected = 2
changeSelection(1)
end
end,
[28] = function () -- enter/return
transmit(CHANNEL_ELEVATOR, os.getComputerID(), "UPDATE", (selected == 1))
doUpdate()
end,
[14] = function () -- backspace
return true
end
}
while true do
_, keycode = os.pullEvent("key")
f = keyHandlers[keycode]
if type(f) == "function" then
if f() then return end
end
end
end -- updateMenu()
function doUpdate()
if updateAvailable then
if shell.run("pastebin", "get", "iJWyUQVr", "elevator-main-update.lua") then
fs.delete("elevator-main.lua")
fs.move("elevator-main-update.lua", "elevator-main.lua")
os.reboot()
end
end
end
function unserialise(s)
local t = {}
t.heights = {}
mt = {
-- The table t uses the y coord as it's indices which are therefore not contiguous and so the retrevial order can not be relied upon.
-- The 'heights' table is a contiguously indexed array whos values are the y coords of each floor. This enables us to sort the floors into (reverse) height order.
__newindex = function(t,k,v)
t.heights[#t.heights+1] = k -- k is the height coordinate of the floor
rawset(t,k,v)
end
}
setmetatable(t, mt)
for u in string.gmatch(s, "[^\030]+") do
local iter = string.gmatch(u, "([^\031]+)")
t[tonumber(iter())] = iter()
end
return t
end
function serialise(t)
local s = ""
for i=1,#t.heights do
s = s..t.heights[i].."\031"..t[t.heights[i]].."\030"
end
return s
end
function addFloor(sMessage)
local iter = string.gmatch(sMessage, "([^\031]+)")
local y,label = tonumber(iter()),iter()
floors[y] = label
newFloorTimer = os.startTimer(3)
end
function sortReverse(t)
table.sort(t.heights, function (a,b) return (a > b) end)
for i=1,#t.heights do
if t.heights[i] == y then
t.heights.y = i; break -- t.heights.y stores the index of the current floor in the t.heights table
-- t.heights[t.heights.y] would give the y coordinate of the current floor
-- t[t.heights[t.heights.y]] would give the text label of the current floor
end
end
end
function transmit(sChannel, sReplyChannel, sMessage, tBroadcast)
-- tBroadcast optionaly specifies that "ALL" should be used instead of the elevator ID
-- Messages are sent in this format: "ELEV" elevID/"ALL" messageID [messageBody]
modem.close(sChannel)
modem.transmit(sChannel, sReplyChannel, "ELEV\030"..(tBroadcast and "ALL" or elevID).."\030"..sMessage)
modem.open(sChannel)
end
function pause()
print ("Press any key to continue...")
os.pullEvent("key")
end
eventHandlers = {
modemFunctions = {
["DISCOVER"] = function (sReplyChannel)
-- Respond to elevator ID discovery request
transmit(sReplyChannel, CHANNEL_ELEVATOR, "ID") -- No need for broadcast flag as the code in setup.lua doesn't filter based on elevID
end,
--["ID"] This function doesn't exist here as these messages are only for the setup program
["ANNOUNCE"] = function (sReplyChannel, sMessage)
-- Received new floor announcement broadcast
-- Add floor to local table
addFloor(sMessage)
-- Send reply of own y and label
transmit(sReplyChannel, CHANNEL_ELEVATOR, "REPLY\030"..y.."\031"..floors[y])
end,
["REPLY"] = function (_, sMessage)
-- Received a reply to own announcement
addFloor(sMessage)
end,
["ACTIVATE"] = function (sReplyChannel, sMessage)
-- Elevator activation message from another floor
acceptInput = false
-- Check if this floor is the destination
if tonumber(sMessage) == y then
ignoreDetector = false
rs.setBundledOutput(bundleSide, colors[elevatorWire])
term.clear()
writeToPos(19,7,"Incoming cart")
writeToPos(15,9,"Please clear the track")
else
displayBusy()
end
end,
["CALL"] = function ()
-- Received cart call message
acceptInput = false
displayBusy()
-- Pulse the boarding rail to send any cart that might be on it
rs.setBundledOutput(bundleSide, colors[boardingWire])
sleep(1)
rs.setBundledOutput(bundleSide, 0)
end,
["CLEAR"] = function ()
-- Recaived notifcation that a cart has arrived at another floor (and so the elevator is probably clear for use)
if departedTimer then departedTimer = nil end
acceptInput = true
renderFloorList(true)
end,
["UPDATE"] = function (sReplyChannel, _, eID)
if not ignoreUpdates then
ignoreUpdates = true
ignoreUpdateTimer = os.startTimer(3)
transmit(CHANNEL_ELEVATOR, sReplyChannel, "UPDATE", eID == "ALL")
doUpdate()
end
end
},
modem_message =
function (_, sChannel, sReplyChannel, sMessage)
if sChannel == CHANNEL_ELEVATOR or sChannel == os.getComputerID() then
local iter = string.gmatch(sMessage, "([^\030]+)")
if iter() ~= "ELEV" then return end -- Right channel but not meant for us
local eID = iter()
if eID == elevID or eID == "ALL" then -- Correct elevID or message is for all elevators
local f = eventHandlers.modemFunctions[iter()]
if type(f) == "function" then
return f(sReplyChannel, iter(), eID) -- iter() = rest of the message
end
end
-- Reply to GPS ping if we know our full coords
elseif z and sChannel == gps.CHANNEL_GPS and sMessage == "PING" then
modem.transmit(sReplyChannel, gps.CHANNEL_GPS, textutils.serialize({x,y,z}))
end
end,
key =
function (keycode)
if acceptInput then
if (keycode == 200) then -- up
if selected > 1 then
if selected-1 == floors.heights.y then
if selected > 2 then selected = selected-2 end
else
selected = selected-1
end
renderFloorList()
end
elseif (keycode == 208) then -- down
if selected < #floors.heights then
if selected+1 == floors.heights.y then
if selected < #floors.heights-1 then selected = selected+2 end
else
selected = selected+1
end
renderFloorList()
end
elseif (keycode == 57) then -- Space
ignoreDetector = false
acceptInput = false
rs.setBundledOutput(bundleSide, colors[elevatorWire])
transmit(CHANNEL_ELEVATOR, os.getComputerID(), "CALL")
term.clear()
writeToPos(20,8,"Cart called")
elseif keycode == 28 and selected ~= floors.heights.y then -- enter
transmit(CHANNEL_ELEVATOR, os.getComputerID(), "ACTIVATE\030"..floors.heights[selected])
rs.setBundledOutput(bundleSide, colors[boardingWire])
acceptInput = false
term.clear()
writeToPos(20,8,"Bon Voyage")
writeToPos(20,10,"Press Esc")
sleep(1)
rs.setBundledOutput(bundleSide, 0)
departedTimer = os.startTimer(2)
elseif keycode == 22 then -- U for update menu
updateMenu()
renderFloorList()
end
end
end,
timer =
function (timerID)
if timerID == newFloorTimer then
newFloorTimer = nil
sortReverse(floors)
local file = io.open("elevator.cfg", "w")
file:write(modemSide.."\n")
file:write(elevID.."\n")
file:write(coords.."\n")
file:write(serialise(floors))
file:close()
renderFloorList(true)
elseif timerID == departedTimer then
departedTimer = nil
displayBusy()
elseif timerID == ignoreUpdateTimer then
ignoreUpdates = false
end
end,
redstone =
function()
if ignoreDetector == false and colors.test(redstone.getBundledInput(bundleSide), colors[detetctorWire]) then
ignoreDetector = true
transmit(CHANNEL_ELEVATOR, os.getComputerID(), "CLEAR")
redstone.setBundledOutput(bundleSide, 0)
renderFloorList(true)
end
end,
handle =
function (self, f, ...)
f = self[f]
if type(f) == "function" then return f(...) end
end
}
function displayBusy()
term.clear()
writeToPos(19,7,"Elevator busy")
writeToPos(20,9,"Please wait")
end
function renderFloorList(reset)
if reset then selected = floors.heights.y end
term.clear()
if updateAvailable then
writeToPos(30,1,"New version available!")
writeToPos(33,2,"Press U for options")
end
local startIndex, line = 1, 1
if selected > 9 then
startIndex = selected - 8
else
line = 10 - selected
end
for i=startIndex,#floors.heights do
term.setCursorPos(13-(tostring(floors.heights[i]):len()),line)
term.write(floors.heights[i]..": "..floors[floors.heights[i]])
line = line + 1
if line == 17 then break end -- If we get to the bottom of the screen, stop printing floors
end
if selected ~= floors.heights.y then
midMarkL, midMarkR = ">", "<"
line = 9 - (selected - floors.heights.y) -- Eg we're on 20, selected is 19: 19 is now at line 9, 20 is at 10 which is 9 - (-1)
writeToPos(8,line,"-")
writeToPos(42,line,"-")
else
midMarkL, midMarkR = "-", "-"
end
writeToPos(8,9,midMarkL)
writeToPos(42,9,midMarkR)
writeToPos(2,18,"Up/Down arrow keys to select destination")
writeToPos(2,19,"Press Enter to activate Space to call cart")
acceptInput = true;
end -- renderFloorList()
floors = unserialise(floors)
sortReverse(floors)
-- Announce self to other floors
modem.open(os.getComputerID())
transmit(CHANNEL_ELEVATOR, os.getComputerID(), "ANNOUNCE\030"..y.."\031"..floors[y])
selected = floors.heights.y
if #floors.heights == 1 then
-- This floor only knows about it's self
term.clear()
writeToPos(7,4,"No other floors discovered")
writeToPos(7,6,"Once the elevator program is started")
writeToPos(7,7,"on other floors, they will appear here.")
else
renderFloorList()
end
while true do
eventHandlers:handle(os.pullEvent())
end