Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
ComputercraftLua/rom/programs/rednet/chat
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
435 lines (392 sloc)
14.1 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local tArgs = { ... } | |
| local function printUsage() | |
| print( "Usages:" ) | |
| print( "chat host <hostname>" ) | |
| print( "chat join <hostname> <nickname>" ) | |
| end | |
| local sOpenedModem = nil | |
| local function openModem() | |
| for n,sModem in ipairs( peripheral.getNames() ) do | |
| if peripheral.getType( sModem ) == "modem" then | |
| if not rednet.isOpen( sModem ) then | |
| rednet.open( sModem ) | |
| sOpenedModem = sModem | |
| end | |
| return true | |
| end | |
| end | |
| print( "No modems found." ) | |
| return false | |
| end | |
| local function closeModem() | |
| if sOpenedModem ~= nil then | |
| rednet.close( sOpenedModem ) | |
| sOpenedModem = nil | |
| end | |
| end | |
| -- Colours | |
| local highlightColour, textColour | |
| if term.isColour() then | |
| textColour = colours.white | |
| highlightColour = colours.yellow | |
| else | |
| textColour = colours.white | |
| highlightColour = colours.white | |
| end | |
| local sCommand = tArgs[1] | |
| if sCommand == "host" then | |
| -- "chat host" | |
| -- Get hostname | |
| local sHostname = tArgs[2] | |
| if sHostname == nil then | |
| printUsage() | |
| return | |
| end | |
| -- Host server | |
| if not openModem() then | |
| return | |
| end | |
| rednet.host( "chat", sHostname ) | |
| print( "0 users connected." ) | |
| local tUsers = {} | |
| local nUsers = 0 | |
| function send( sText, nUserID ) | |
| if nUserID then | |
| local tUser = tUsers[ nUserID ] | |
| if tUser then | |
| rednet.send( tUser.nID, { | |
| sType = "text", | |
| nUserID = nUserID, | |
| sText = sText, | |
| }, "chat" ) | |
| end | |
| else | |
| for nUserID, tUser in pairs( tUsers ) do | |
| rednet.send( tUser.nID, { | |
| sType = "text", | |
| nUserID = nUserID, | |
| sText = sText, | |
| }, "chat" ) | |
| end | |
| end | |
| end | |
| -- Setup ping pong | |
| local tPingPongTimer = {} | |
| function ping( nUserID ) | |
| local tUser = tUsers[ nUserID ] | |
| rednet.send( tUser.nID, { | |
| sType = "ping to client", | |
| nUserID = nUserID, | |
| }, "chat" ) | |
| local timer = os.startTimer( 15 ) | |
| tUser.bPingPonged = false | |
| tPingPongTimer[ timer ] = nUserID | |
| end | |
| function printUsers() | |
| local x,y = term.getCursorPos() | |
| term.setCursorPos( 1, y - 1 ) | |
| term.clearLine() | |
| if nUsers == 1 then | |
| print( nUsers .. " user connected." ) | |
| else | |
| print( nUsers .. " users connected." ) | |
| end | |
| end | |
| -- Handle messages | |
| local ok, error = pcall( function() | |
| parallel.waitForAny( function() | |
| while true do | |
| local sEvent, timer = os.pullEvent( "timer" ) | |
| local nUserID = tPingPongTimer[ timer ] | |
| if nUserID and tUsers[ nUserID ] then | |
| local tUser = tUsers[ nUserID ] | |
| if tUser then | |
| if not tUser.bPingPonged then | |
| send( "* "..tUser.sUsername.." has timed out" ) | |
| tUsers[ nUserID ] = nil | |
| nUsers = nUsers - 1 | |
| printUsers() | |
| else | |
| ping( nUserID ) | |
| end | |
| end | |
| end | |
| end | |
| end, | |
| function() | |
| while true do | |
| local tCommands | |
| tCommands = { | |
| ["me"] = function( tUser, sContent ) | |
| if string.len(sContent) > 0 then | |
| send( "* "..tUser.sUsername.." "..sContent ) | |
| else | |
| send( "* Usage: /me [words]", tUser.nUserID ) | |
| end | |
| end, | |
| ["nick"] = function( tUser, sContent ) | |
| if string.len(sContent) > 0 then | |
| local sOldName = tUser.sUsername | |
| tUser.sUsername = sContent | |
| send( "* "..sOldName.." is now known as "..tUser.sUsername ) | |
| else | |
| send( "* Usage: /nick [nickname]", tUser.nUserID ) | |
| end | |
| end, | |
| ["users"] = function( tUser, sContent ) | |
| send( "* Connected Users:", tUser.nUserID ) | |
| local sUsers = "*" | |
| for nUserID, tUser in pairs( tUsers ) do | |
| sUsers = sUsers .. " " .. tUser.sUsername | |
| end | |
| send( sUsers, tUser.nUserID ) | |
| end, | |
| ["help"] = function( tUser, sContent ) | |
| send( "* Available commands:", tUser.nUserID ) | |
| local sCommands = "*" | |
| for sCommand, fnCommand in pairs( tCommands ) do | |
| sCommands = sCommands .. " /" .. sCommand | |
| end | |
| send( sCommands.." /logout", tUser.nUserID ) | |
| end, | |
| } | |
| local nSenderID, tMessage = rednet.receive( "chat" ) | |
| if type( tMessage ) == "table" then | |
| if tMessage.sType == "login" then | |
| -- Login from new client | |
| local nUserID = tMessage.nUserID | |
| local sUsername = tMessage.sUsername | |
| if nUserID and sUsername then | |
| tUsers[ nUserID ] = { | |
| nID = nSenderID, | |
| nUserID = nUserID, | |
| sUsername = sUsername, | |
| } | |
| nUsers = nUsers + 1 | |
| printUsers() | |
| send( "* "..sUsername.." has joined the chat" ) | |
| ping( nUserID ) | |
| end | |
| else | |
| -- Something else from existing client | |
| local nUserID = tMessage.nUserID | |
| local tUser = tUsers[ nUserID ] | |
| if tUser and tUser.nID == nSenderID then | |
| if tMessage.sType == "logout" then | |
| send( "* "..tUser.sUsername.." has left the chat" ) | |
| tUsers[ nUserID ] = nil | |
| nUsers = nUsers - 1 | |
| printUsers() | |
| elseif tMessage.sType == "chat" then | |
| local sMessage = tMessage.sText | |
| if sMessage then | |
| local sCommand = string.match( sMessage, "^/([a-z]+)" ) | |
| if sCommand then | |
| local fnCommand = tCommands[ sCommand ] | |
| if fnCommand then | |
| local sContent = string.sub( sMessage, string.len(sCommand)+3 ) | |
| fnCommand( tUser, sContent ) | |
| else | |
| send( "* Unrecognised command: /"..sCommand, tUser.nUserID ) | |
| end | |
| else | |
| send( "<"..tUser.sUsername.."> "..tMessage.sText ) | |
| end | |
| end | |
| elseif tMessage.sType == "ping to server" then | |
| rednet.send( tUser.nID, { | |
| sType = "pong to client", | |
| nUserID = nUserID, | |
| }, "chat" ) | |
| elseif tMessage.sType == "pong to server" then | |
| tUser.bPingPonged = true | |
| end | |
| end | |
| end | |
| end | |
| end | |
| end ) | |
| end ) | |
| if not ok then | |
| printError( error ) | |
| end | |
| -- Unhost server | |
| for nUserID, tUser in pairs( tUsers ) do | |
| rednet.send( tUser.nID, { | |
| sType = "kick", | |
| nUserID = nUserID, | |
| }, "chat" ) | |
| end | |
| rednet.unhost( "chat" ) | |
| closeModem() | |
| elseif sCommand == "join" then | |
| -- "chat join" | |
| -- Get hostname and username | |
| local sHostname = tArgs[2] | |
| local sUsername = tArgs[3] | |
| if sHostname == nil or sUsername == nil then | |
| printUsage() | |
| return | |
| end | |
| -- Connect | |
| if not openModem() then | |
| return | |
| end | |
| write( "Looking up " .. sHostname .. "... " ) | |
| local nHostID = rednet.lookup( "chat", sHostname ) | |
| if nHostID == nil then | |
| print( "Failed." ) | |
| return | |
| else | |
| print( "Success." ) | |
| end | |
| -- Login | |
| local nUserID = math.random( 1, 2147483647 ) | |
| rednet.send( nHostID, { | |
| sType = "login", | |
| nUserID = nUserID, | |
| sUsername = sUsername, | |
| }, "chat" ) | |
| -- Setup ping pong | |
| local bPingPonged = true | |
| local pingPongTimer = os.startTimer( 0 ) | |
| function ping() | |
| rednet.send( nHostID, { | |
| sType = "ping to server", | |
| nUserID = nUserID, | |
| }, "chat" ) | |
| bPingPonged = false | |
| pingPongTimer = os.startTimer( 15 ) | |
| end | |
| -- Handle messages | |
| local w,h = term.getSize() | |
| local parentTerm = term.current() | |
| local titleWindow = window.create( parentTerm, 1, 1, w, 1, true ) | |
| local historyWindow = window.create( parentTerm, 1, 2, w, h-2, true ) | |
| local promptWindow = window.create( parentTerm, 1, h, w, 1, true ) | |
| historyWindow.setCursorPos( 1, h-2 ) | |
| term.clear() | |
| term.setTextColour( textColour ) | |
| term.redirect( promptWindow ) | |
| promptWindow.restoreCursor() | |
| function drawTitle() | |
| local x,y = titleWindow.getCursorPos() | |
| local w,h = titleWindow.getSize() | |
| local sTitle = sUsername.." on "..sHostname | |
| titleWindow.setTextColour( highlightColour ) | |
| titleWindow.setCursorPos( math.floor( w/2 - string.len(sTitle)/2 ), 1 ) | |
| titleWindow.clearLine() | |
| titleWindow.write( sTitle ) | |
| promptWindow.restoreCursor() | |
| end | |
| function printMessage( sMessage ) | |
| term.redirect( historyWindow ) | |
| print() | |
| if string.match( sMessage, "^\*" ) then | |
| -- Information | |
| term.setTextColour( highlightColour ) | |
| write( sMessage ) | |
| term.setTextColour( textColour ) | |
| else | |
| -- Chat | |
| local sUsernameBit = string.match( sMessage, "^\<[^\>]*\>" ) | |
| if sUsernameBit then | |
| term.setTextColour( highlightColour ) | |
| write( sUsernameBit ) | |
| term.setTextColour( textColour ) | |
| write( string.sub( sMessage, string.len( sUsernameBit ) + 1 ) ) | |
| else | |
| write( sMessage ) | |
| end | |
| end | |
| term.redirect( promptWindow ) | |
| promptWindow.restoreCursor() | |
| end | |
| drawTitle() | |
| local ok, error = pcall( function() | |
| parallel.waitForAny( function() | |
| while true do | |
| local sEvent, timer = os.pullEvent() | |
| if sEvent == "timer" then | |
| if timer == pingPongTimer then | |
| if not bPingPonged then | |
| printMessage( "Server timeout." ) | |
| return | |
| else | |
| ping() | |
| end | |
| end | |
| elseif sEvent == "term_resize" then | |
| local w,h = parentTerm.getSize() | |
| titleWindow.reposition( 1, 1, w, 1 ) | |
| historyWindow.reposition( 1, 2, w, h-2 ) | |
| promptWindow.reposition( 1, h, w, 1 ) | |
| end | |
| end | |
| end, | |
| function() | |
| while true do | |
| local nSenderID, tMessage = rednet.receive( "chat" ) | |
| if nSenderID == nHostID and type( tMessage ) == "table" and tMessage.nUserID == nUserID then | |
| if tMessage.sType == "text" then | |
| local sText = tMessage.sText | |
| if sText then | |
| printMessage( sText ) | |
| end | |
| elseif tMessage.sType == "ping to client" then | |
| rednet.send( nSenderID, { | |
| sType = "pong to server", | |
| nUserID = nUserID, | |
| }, "chat" ) | |
| elseif tMessage.sType == "pong to client" then | |
| bPingPonged = true | |
| elseif tMessage.sType == "kick" then | |
| return | |
| end | |
| end | |
| end | |
| end, | |
| function() | |
| local tSendHistory = {} | |
| while true do | |
| promptWindow.setCursorPos( 1,1 ) | |
| promptWindow.clearLine() | |
| promptWindow.setTextColor( highlightColour ) | |
| promptWindow.write( ": ") | |
| promptWindow.setTextColor( textColour ) | |
| local sChat = read( nil, tSendHistory ) | |
| if string.match( sChat, "^/logout" ) then | |
| break | |
| else | |
| rednet.send( nHostID, { | |
| sType = "chat", | |
| nUserID = nUserID, | |
| sText = sChat, | |
| }, "chat" ) | |
| table.insert( tSendHistory, sChat ) | |
| end | |
| end | |
| end ) | |
| end ) | |
| -- Close the windows | |
| term.redirect( parentTerm ) | |
| -- Print error notice | |
| local w,h = term.getSize() | |
| term.setCursorPos( 1, h ) | |
| term.clearLine() | |
| term.setCursorBlink( false ) | |
| if not ok then | |
| printError( error ) | |
| end | |
| -- Logout | |
| rednet.send( nHostID, { | |
| sType = "logout", | |
| nUserID = nUserID, | |
| }, "chat" ) | |
| closeModem() | |
| -- Print disconnection notice | |
| print( "Disconnected." ) | |
| else | |
| -- "chat somethingelse" | |
| printUsage() | |
| end |