CC-Tweaked/src/main/resources/assets/computercraft/lua/rom/programs/rednet/chat.lua

435 lines
14 KiB
Lua

local tArgs = { ... }
local function printUsage()
print( "Usages:" )
print( "chat host <hostname>" )
print( "chat join <hostname> <nickname>" )
end
local sOpenedModem = nil
local function openModem()
for _,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
local 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 = {}
local 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
local function printUsers()
local _,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 _, 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 _, 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 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 )
local 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()
local function drawTitle()
local w = 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
local 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 _,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