SHOW:
|
|
- or go back to the newest paste.
1 | -- [ --------------------------------------------------------------------- ] -- | |
2 | -- [ RedNetChat - A public chat program for ComputerCraft ] -- | |
3 | -- [ Created by gpgautier (ign gpgauier), 2012 ] -- | |
4 | -- [ Licence: Creative Commons Attribution-ShareAlike 3.0 Unported License ] -- | |
5 | -- [ ComputerCraft version: 1.3 and up ] -- | |
6 | -- [ Posted to: ] -- | |
7 | -- [ GitHub: https://github.com/gpgautier/RedNetChat ] -- | |
8 | -- [ Pastebin: http://pastebin.com/JDU4wJxX ] -- | |
9 | -- [ --------------------------------------------------------------------- ] -- | |
10 | ||
11 | local VERSION = "0.6" | |
12 | local MODEM = nil | |
13 | local NICKNAME = nil | |
14 | local ACTIVE = false | |
15 | local BUFFER = {} | |
16 | local POINTER = 0 | |
17 | local ONLINE = {} | |
18 | local ISONLINE = false | |
19 | local ID = os.computerID() | |
20 | local LAST_MSG_TARGET = nil | |
21 | local CHANNEL = 1 | |
22 | local SCROLL_POINTER = POINTER | |
23 | local WIDTH, HEIGHT = term.getSize() | |
24 | local LINES = HEIGHT - 6 | |
25 | local START_LINE = 5 | |
26 | local OPERATOR = "RNC" | |
27 | ||
28 | -- [ --------------------------------------------------------------------- ] -- | |
29 | ||
30 | -- Split a string | |
31 | function split(str, pat) | |
32 | local t = {} -- NOTE: use {n = 0} in Lua-5.0 | |
33 | if str ~= nil then | |
34 | local fpat = "(.-)" .. pat | |
35 | local last_end = 1 | |
36 | local s, e, cap = str:find(fpat, 1) | |
37 | while s do | |
38 | if s ~= 1 or cap ~= "" then | |
39 | table.insert(t,cap) | |
40 | end | |
41 | last_end = e+1 | |
42 | s, e, cap = str:find(fpat, last_end) | |
43 | end | |
44 | if last_end <= #str then | |
45 | cap = str:sub(last_end) | |
46 | table.insert(t, cap) | |
47 | end | |
48 | else | |
49 | print("##ERROR failed to split ["..str.."] by:"..pat) | |
50 | end | |
51 | return t | |
52 | end | |
53 | ||
54 | -- Log a message to file | |
55 | function log(message) | |
56 | local file = io.open("rednetchat.log", "a") | |
57 | file:write("\n" .. message) | |
58 | file:close() | |
59 | end | |
60 | ||
61 | -- Application entry | |
62 | function main() | |
63 | term.clear() | |
64 | term.setCursorPos(1, 1) | |
65 | ||
66 | if not setPeripherals() then | |
67 | print("[FATAL ERROR] Not able to setup peripherals.") | |
68 | return false | |
69 | end | |
70 | ||
71 | welcome() | |
72 | end | |
73 | ||
74 | -- Set the attached peripherals. Opens rednet modem and warps monitor | |
75 | function setPeripherals() | |
76 | local i, side | |
77 | ||
78 | for i, side in pairs(rs.getSides()) do | |
79 | if peripheral.isPresent(side) then | |
80 | if peripheral.getType(side) == "modem" then | |
81 | MODEM = side | |
82 | if not rednet.isOpen(side) then | |
83 | rednet.open(MODEM) | |
84 | end | |
85 | end | |
86 | end | |
87 | end | |
88 | ||
89 | -- Exit with a fatal error when modem not found | |
90 | if MODEM == nil then | |
91 | print("[FATAL ERROR] No modem was detected. Plase attach a modem on any side.") | |
92 | return false | |
93 | end | |
94 | ||
95 | return true | |
96 | end | |
97 | ||
98 | -- Start the welcome screen | |
99 | function welcome() | |
100 | local x, y | |
101 | ||
102 | term.clear() | |
103 | writeHeader() | |
104 | ||
105 | print("") | |
106 | print("") | |
107 | print("Enter a nickname and press [enter].") | |
108 | print("") | |
109 | term.write("Nickname: ") | |
110 | ||
111 | x, y = term.getCursorPos() | |
112 | ||
113 | while NICKNAME == nil or NICKNAME == "" do | |
114 | term.setCursorPos(x, y) | |
115 | NICKNAME = read() | |
116 | execute("/online") | |
117 | appendBuffer("[" .. OPERATOR .. "]: Type /help for a list of commands") | |
118 | end | |
119 | ||
120 | start() | |
121 | end | |
122 | ||
123 | -- Writes the screen header | |
124 | function writeHeader() | |
125 | local col | |
126 | ||
127 | term.setCursorPos(1, 1) | |
128 | term.write("RedNet Chat " .. VERSION .. "") | |
129 | term.setCursorPos(1, 2) | |
130 | ||
131 | for col = 1, WIDTH do | |
132 | term.write("-") | |
133 | end | |
134 | end | |
135 | ||
136 | -- Writes the list of online users | |
137 | function writeOnlineList() | |
138 | local i, v, count, x, y, col | |
139 | ||
140 | count = 0 | |
141 | ||
142 | x, y = term.getCursorPos() | |
143 | ||
144 | term.setCursorPos(1, HEIGHT - 1) | |
145 | ||
146 | for col = 1, WIDTH do | |
147 | term.write("-") | |
148 | end | |
149 | ||
150 | term.setCursorPos(1, HEIGHT) | |
151 | term.clearLine() | |
152 | term.write("Online: ") | |
153 | ||
154 | for i, v in pairs(ONLINE) do | |
155 | if count == 0 then | |
156 | term.write(i) | |
157 | else | |
158 | term.write(", " .. i) | |
159 | end | |
160 | ||
161 | count = count + 1 | |
162 | end | |
163 | ||
164 | if count == 0 then | |
165 | term.write("Nobody online in channel " .. CHANNEL) | |
166 | end | |
167 | ||
168 | term.setCursorPos(x, y) | |
169 | end | |
170 | ||
171 | -- Start the chat | |
172 | function start() | |
173 | term.clear() | |
174 | writeHeader() | |
175 | writeOnlineList() | |
176 | ||
177 | ACTIVE = true | |
178 | ||
179 | showBuffer() | |
180 | ||
181 | parallel.waitForAll(input, watchEvents) | |
182 | end | |
183 | ||
184 | -- Stop the application | |
185 | function stop() | |
186 | ACTIVE = false | |
187 | end | |
188 | ||
189 | -- Reset the application | |
190 | function reset() | |
191 | execute("/offline") | |
192 | ||
193 | if rednet.isOpen(MODEM) then | |
194 | rednet.close(MODEM) | |
195 | end | |
196 | ||
197 | sleep(1.5) | |
198 | os.reboot() | |
199 | end | |
200 | ||
201 | -- Watch all input to provide possible shortcuts (for example usernames) | |
202 | function watchEvents() | |
203 | local type, param, param2, param3, i, v | |
204 | ||
205 | while ACTIVE do | |
206 | type, param, param2, param3 = os.pullEvent() | |
207 | ||
208 | if type == "key" then | |
209 | if param == 200 then -- up | |
210 | scroll(-1) | |
211 | elseif param == 208 then -- down | |
212 | scroll(1) | |
213 | elseif param == 201 then -- pageup | |
214 | scroll(-12) | |
215 | elseif param == 209 then -- pagedown | |
216 | scroll(12) | |
217 | --else | |
218 | -- appendBuffer(tostring(param)) | |
219 | end | |
220 | elseif type == "mouse_scroll" then | |
221 | if param == -1 then | |
222 | scroll(-1) | |
223 | else | |
224 | scroll(1) | |
225 | end | |
226 | elseif type == "rednet_message" then | |
227 | receive(param2) | |
228 | end | |
229 | end | |
230 | end | |
231 | ||
232 | -- Scroll through the chat | |
233 | function scroll(amount) | |
234 | SCROLL_POINTER = SCROLL_POINTER + amount | |
235 | showBuffer() | |
236 | end | |
237 | ||
238 | -- Handle input from the prompt | |
239 | function input() | |
240 | local message, col | |
241 | ||
242 | term.setCursorPos(1, 4) | |
243 | ||
244 | for col = 1, WIDTH do | |
245 | term.write("-") | |
246 | end | |
247 | ||
248 | while ACTIVE do | |
249 | term.setCursorPos(1, 3) | |
250 | term.clearLine() | |
251 | term.write("[" .. CHANNEL .. "] > ") | |
252 | ||
253 | message = read() | |
254 | ||
255 | if message ~= nil and message ~= "" then | |
256 | execute(message, "local") | |
257 | end | |
258 | end | |
259 | end | |
260 | ||
261 | -- Send a message | |
262 | function send(message, target) | |
263 | local request, serialized, x, encrypted | |
264 | ||
265 | request = {protocol = "rnc", nickname = NICKNAME, sender = ID, target = target, channel = CHANNEL, message = message} | |
266 | serialized = textutils.serialize(request) | |
267 | ||
268 | encrypted = "" | |
269 | for x = 1, #serialized do | |
270 | encrypted = encrypted .. string.char(serialized:byte(x) + 1) | |
271 | end | |
272 | ||
273 | if request.target ~= nil then | |
274 | rednet.send(request.target, encrypted) | |
275 | else | |
276 | rednet.broadcast(encrypted) | |
277 | end | |
278 | end | |
279 | ||
280 | -- Recieve a message | |
281 | function receive(message) | |
282 | local request, decrypted, x | |
283 | ||
284 | if message ~= nil and message ~= "" then | |
285 | ||
286 | decrypted = "" | |
287 | for x = 1, #message do | |
288 | decrypted = decrypted .. string.char(message:byte(x) - 1) | |
289 | end | |
290 | ||
291 | request = textutils.unserialize(decrypted) | |
292 | ||
293 | if request.protocol == "rnc" and request.channel == CHANNEL then | |
294 | if request.nickname ~= nil and request.nickname ~= "" then | |
295 | execute(request, "remote") | |
296 | end | |
297 | end | |
298 | end | |
299 | end | |
300 | ||
301 | -- Execute a command or add a chat message | |
302 | function execute(message, source) | |
303 | local command, splitCommand, nickname, id, body, onlineUser | |
304 | ||
305 | if message.nickname ~= nil then | |
306 | executeRemote(message) | |
307 | return | |
308 | end | |
309 | ||
310 | if message:sub(0, 1) == "/" then | |
311 | command = message:sub(2) | |
312 | ||
313 | if command == "quit" | |
314 | or command == "reset" | |
315 | or command == "restart" | |
316 | or command == "reboot" | |
317 | or command == "stop" | |
318 | then | |
319 | appendBuffer("[" .. OPERATOR .. "]: Stopping application") | |
320 | reset() | |
321 | elseif command == "online" then | |
322 | if not ISONLINE then | |
323 | send("/online") | |
324 | putOnline() | |
325 | appendBuffer("[" .. OPERATOR .. "]: You are now online") | |
326 | ISONLINE = true | |
327 | else | |
328 | appendBuffer("[" .. OPERATOR .. "]: You are already online") | |
329 | end | |
330 | elseif command == "offline" then | |
331 | if ISONLINE then | |
332 | send("/offline") | |
333 | takeOffline() | |
334 | appendBuffer("[" .. OPERATOR .. "]: You are now offline") | |
335 | ISONLINE = false | |
336 | else | |
337 | appendBuffer("[" .. OPERATOR .. "]: You are already offline") | |
338 | end | |
339 | elseif command:sub(0, 5) == "nick " then | |
340 | takeOffline() | |
341 | NICKNAME = command:sub(6) | |
342 | putOnline() | |
343 | appendBuffer("[" .. OPERATOR .. "]: Your nickname has been changed") | |
344 | elseif command:sub(0, 5) == "slap " then | |
345 | appendBuffer(command:sub(6) .. " was slapped by " .. NICKNAME) | |
346 | elseif command:sub(0, 4) == "msg " then | |
347 | splitCommand = split(command:sub(5), "%s") | |
348 | ||
349 | onlineUser = false | |
350 | ||
351 | for nickname, id in pairs(ONLINE) do | |
352 | if nickname == splitCommand[1] then | |
353 | body = command:sub(5 + splitCommand[1]:len() + 1) | |
354 | send(body, id) | |
355 | appendBuffer(NICKNAME .. " > " .. nickname .. ": " .. body) | |
356 | onlineUser = true | |
357 | LAST_MSG_TARGET = nickname | |
358 | end | |
359 | end | |
360 | ||
361 | if not onlineUser then | |
362 | appendBuffer("[" .. OPERATOR .. "]: User " .. splitCommand[1] .. " is not online") | |
363 | end | |
364 | elseif command:sub(0, 2) == "r " then | |
365 | if LAST_MSG_TARGET ~= nil then | |
366 | execute("/msg " .. LAST_MSG_TARGET .. " " .. command:sub(3), "local") | |
367 | else | |
368 | appendBuffer("[" .. OPERATOR .. "]: No valid user for message") | |
369 | end | |
370 | elseif command:sub(0, 5) == "join " then | |
371 | if CHANNEL ~= tonumber(command:sub(6)) then | |
372 | execute("/offline") | |
373 | CHANNEL = tonumber(command:sub(6)) | |
374 | execute("/online") | |
375 | appendBuffer("[" .. OPERATOR .. "]: Joined channel " .. CHANNEL) | |
376 | else | |
377 | appendBuffer("[" .. OPERATOR .. "]: Already in channel " .. CHANNEL) | |
378 | end | |
379 | elseif command == "help" then | |
380 | appendBuffer("[" .. OPERATOR .. "] Commands:") | |
381 | appendBuffer("/quit : Exit the chat") | |
382 | appendBuffer("/msg <nickname> <message> : Send a private message") | |
383 | appendBuffer("/r <message> : Reply to a private message") | |
384 | appendBuffer("/join <channel> : Switch channel") | |
385 | else | |
386 | appendBuffer("[" .. OPERATOR .. "]: Unknown command") | |
387 | end | |
388 | ||
389 | return | |
390 | end | |
391 | ||
392 | appendBuffer(NICKNAME .. ": " .. message) | |
393 | send(message) | |
394 | end | |
395 | ||
396 | -- | |
397 | function putOnline(nickname, id) | |
398 | if nickname == nil or id == nil then | |
399 | nickname = NICKNAME | |
400 | id = ID | |
401 | end | |
402 | ||
403 | ONLINE[nickname] = id | |
404 | ||
405 | writeOnlineList() | |
406 | end | |
407 | ||
408 | -- | |
409 | function takeOffline(nickname, id) | |
410 | if nickname == nil or id == nil then | |
411 | nickname = NICKNAME | |
412 | id = ID | |
413 | end | |
414 | ||
415 | ONLINE[nickname] = nil | |
416 | ||
417 | writeOnlineList() | |
418 | end | |
419 | ||
420 | -- | |
421 | function executeRemote(request) | |
422 | local command | |
423 | ||
424 | if request.message:sub(0, 1) == "/" then | |
425 | command = request.message:sub(2) | |
426 | ||
427 | if command == "online" then | |
428 | putOnline(request.nickname, request.sender) | |
429 | appendBuffer("[" .. OPERATOR .. "]: " .. request.nickname .. " is now online") | |
430 | send("/metoo") | |
431 | elseif command == "offline" then | |
432 | takeOffline(request.nickname, request.sender) | |
433 | appendBuffer("[" .. OPERATOR .. "]: " .. request.nickname .. " is now offline") | |
434 | elseif command == "metoo" then | |
435 | putOnline(request.nickname, request.sender) | |
436 | end | |
437 | return | |
438 | end | |
439 | ||
440 | if request.target ~= nil then | |
441 | appendBuffer(request.nickname .. " > " .. NICKNAME .. ": " .. request.message) | |
442 | LAST_MSG_TARGET = request.nickname | |
443 | else | |
444 | appendBuffer(request.nickname .. ": " .. request.message) | |
445 | end | |
446 | end | |
447 | ||
448 | -- | |
449 | function appendBuffer(message) | |
450 | local length | |
451 | ||
452 | length = message:len() | |
453 | ||
454 | if length > WIDTH then | |
455 | table.insert(BUFFER, message:sub(1, WIDTH)) | |
456 | POINTER = POINTER + 1 | |
457 | appendBuffer(message:sub(WIDTH + 1)) | |
458 | else | |
459 | table.insert(BUFFER, message) | |
460 | POINTER = POINTER + 1 | |
461 | end | |
462 | ||
463 | SCROLL_POINTER = POINTER | |
464 | ||
465 | showBuffer() | |
466 | end | |
467 | ||
468 | -- | |
469 | function showBuffer() | |
470 | local i, line, bufferPointer, x, y, pointer | |
471 | ||
472 | pointer = SCROLL_POINTER | |
473 | ||
474 | if pointer == 0 then | |
475 | return | |
476 | elseif SCROLL_POINTER > POINTER then | |
477 | SCROLL_POINTER = POINTER | |
478 | pointer = POINTER | |
479 | elseif POINTER < LINES + 1 then | |
480 | SCROLL_POINTER = POINTER | |
481 | pointer = POINTER | |
482 | elseif POINTER > LINES and SCROLL_POINTER < LINES then | |
483 | SCROLL_POINTER = LINES | |
484 | pointer = SCROLL_POINTER | |
485 | end | |
486 | ||
487 | x, y = term.getCursorPos() | |
488 | ||
489 | line = START_LINE | |
490 | ||
491 | bufferPointer = -(LINES - 1 - pointer) | |
492 | ||
493 | for i = bufferPointer, bufferPointer + (LINES - 1) do | |
494 | term.setCursorPos(1, line) | |
495 | term.clearLine() | |
496 | ||
497 | if BUFFER[i] ~= nil then | |
498 | term.write(tostring(BUFFER[i])) | |
499 | end | |
500 | ||
501 | line = line + 1 | |
502 | end | |
503 | ||
504 | term.setCursorPos(x, y) | |
505 | end | |
506 | ||
507 | -- Fire up the application | |
508 | main() |