class UT2K4IRCLink extends BufferedTCPLink; var IpAddr ServerIpAddr; var string ServerAddress; var int ServerPort; var string NickName; var string UserIdent; var string FullName; var string DefaultChannel; var localized string InvalidAddressText; var localized string ErrorBindingText; var localized string ResolveFailedText; var localized string ConnectedText; var localized string ConnectingToText; var localized string TimeOutError; var localized string InviteString; var UT2K4IRC_System SystemPage; var string DisconnectReason; var string VersionString; struct CommandAlias { var() string AliasText; var() string RealCommand; }; var config array Shortcuts; var transient float SinceLastLevCheck; function PostBeginPlay() { Super.PostBeginPlay(); Disable('Tick'); } function CloseMe() { if ( SystemPage != None ) SystemPage.CloseLink(Self, False); else DestroyLink(); } function Connect(UT2K4IRC_System InSystemPage, string InServer, string InNickName, string InUserIdent, string InFullName, string InDefaultChannel) { local int i; log("UT2K4IRCLink Connect:"@InServer@InNickName@InUserIdent@InFullName@InDefaultChannel,'IRC'); SystemPage = InSystemPage; NickName = InNickName; FullName = InFullName; UserIdent = InUserIdent; DefaultChannel = InDefaultChannel; i = InStr(InServer, ":"); if(i == -1) { ServerAddress = InServer; ServerPort = 6667; } else { ServerAddress = Left(InServer, i); ServerPort = Int(Mid(InServer, i+1)); } ResetBuffer(); ServerIpAddr.Port = ServerPort; SetTimer(20, False); SystemPage.SystemText( ConnectingToText@ServerAddress ); Resolve( ServerAddress ); } function string ChopLeft(string Text) { while(Text != "" && InStr(": !", Left(Text, 1)) != -1) Text = Mid(Text, 1); return Text; } function string RemoveNickPrefix(string Nick) { while(Nick != "" && InStr(":@+%", Left(Nick, 1)) != -1) Nick = Mid(Nick, 1); return Nick; } function string Chop(string Text) { while(Text != "" && InStr(": !", Left(Text, 1)) != -1) Text = Mid(Text, 1); while(Text != "" && InStr(": !", Right(Text, 1)) != -1) Text = Left(Text, Len(Text)-1); return Text; } function Resolved( IpAddr Addr ) { // Set the address ServerIpAddr.Addr = Addr.Addr; // Handle failure. if( ServerIpAddr.Addr == 0 ) { if(SystemPage != None) { SystemPage.SystemText( InvalidAddressText ); CloseMe(); } return; } // Display success message. Log( "UT2004 UT2K4IRCLink: Server is "$ServerAddress$":"$ServerIpAddr.Port,'IRC' ); // Bind the local port. if( BindPort() == 0 ) { if(SystemPage != None) { SystemPage.SystemText( ErrorBindingText ); CloseMe(); } return; } Open( ServerIpAddr ); } event Closed() { CloseMe(); } // Host resolution failue. function ResolveFailed() { if(SystemPage != None) { SystemPage.SystemText(ResolveFailedText); CloseMe(); } } event Timer() { if(SystemPage != None) { SystemPage.SystemText( TimeOutError ); CloseMe(); log("Failed to resolve "$ServerAddress,'IRC'); } return; } event Opened() { SetTimer(0, False); if(SystemPage != None) SystemPage.SystemText(ConnectedText); Enable('Tick'); GotoState('LoggingIn'); } function Tick(float DeltaTime) { local string Line; DoBufferQueueIO(); if(ReadBufferedLine(Line)) ProcessInput(Line); Super.Tick(DeltaTime); if( GetStateName() == 'LoggedIn' ) { // Every 5 seconds, keep the IRC client away string up to date. SinceLastLevCheck += DeltaTime; if(SinceLastLevCheck > 5) { if(SystemPage != None) SystemPage.UpdateAway(); SinceLastLevCheck = 0; } } } function SendCommandText(string Cmd) { local int i; local string Text, Temp; // Add colons for commands: PRIVMSG, QUIT, KILL, KICK, NOTICE Divide(Cmd, " ", Cmd, Text); ReplaceCommandAlias(Cmd); // log("IRCLink::SendCommandText() Cmd:"$Cmd,'IRC'); // log("IRCLink::SendCommandText() Text:"$Text, 'IRC'); switch(Cmd) { // CMD nick :message case "PRIVMSG": case "NOTICE": case "KILL": if ( Text == "" ) break; Text = ChopLeft(Text); if ( Divide(Text, " ", Temp, Text) ) { Temp @= ":" $ ChopLeft(Text); Text = Temp; } break; // disable CTCP case "CTCP": break; // CMD #channel nick :message case "KICK": if ( Text == "" ) break; Text = ChopLeft(Text); if ( Divide(Text, " ", Temp, Text) ) { i = InStr(Text, " "); if (i != -1) Temp @= ":"$Text; // adding reason for kick else if ( Text != "" ) Temp @= Text; Text = Temp; } Text = SystemPage.GetCurrentChannelName() @ Text; // adding channel name break; // CMD :message case "QUIT": if (Text != "") Text = " :"$ChopLeft(Text); break; case "JOIN": JoinChannel(Text); return; case "PART": if ( Text == "" ) Text = SystemPage.GetCurrentChannelName(); PartChannel(Text); return; } if ( Cmd != "" ) SendBufferedData( Cmd $ Eval(Text != "", " " $ Text, "") $ CRLF ); } function ReplaceCommandAlias(out string Text) { local int i; for (i = 0; i < Shortcuts.Length; i++) { if (Shortcuts[i].AliasText ~= Text) { Text = Shortcuts[i].RealCommand; return; } } } function SendBufferedData(string Text) { ReplaceText(Text, "$*", ""); ReplaceText(Text, "#*", ""); // log("IRCLink::SendingBufferedData >"$ Text ); Super.SendBufferedData(Text); } function SendChannelText(string Channel, string Text) { if ( Channel == "" ) Channel = SystemPage.GetCurrentChannelName(); SendBufferedData("PRIVMSG "$Channel$" :"$Text$CRLF); } function SendChannelAction(string Channel, string Text) { if ( Channel == "" ) Channel = SystemPage.GetCurrentChannelName(); if (Left(Channel,1) != "#") Channel = "#" $ Channel; SendBufferedData("PRIVMSG "$Channel$" :"$Chr(1)$"ACTION "$Text$Chr(1)$CRLF); } function ProcessInput(string Line) { // Respond to PING if(Left(Line, 5) == "PING ") SendBufferedData("PONG "$Mid(Line, 5)$CRLF); } state LoggingIn { function Timer() { SendBufferedData("NICK "$NickName$CRLF); SetTimer(1, false); } function ProcessInput(string Line) { local string Temp; log("LoggingIn: "$Line,'IRC'); Temp = ParseDelimited(Line, " ", 2); if(ParseDelimited(Line, " ", 1)== "ERROR") SystemPage.SystemText(ChopLeft(ParseDelimited(Line, ":", 2, True))); if( Temp == "433" ) { // Nick in use SystemPage.SystemText(ChopLeft(ParseDelimited(Line, " ", 3, True))); SetTimer(0, false); SystemPage.NotifyNickInUse(); } else if( Temp=="432" ) { // Invalid nick SystemPage.SystemText(ChopLeft(ParseDelimited(Line, " ", 3, True))); SetTimer(0, false); SystemPage.NotifyInvalidNick(); } else if( SystemPage.IsDigit(Temp) ) { SystemPage.SystemText(ChopLeft(ParseDelimited(Line, " ", 3, True))); SetTimer(0, False); GotoState('LoggedIn'); } Global.ProcessInput(Line); } function SendCommandText(string Text) { if(ParseDelimited(Text, " ", 1) ~= "NICK") { SystemPage.ChangedNick(NickName, Chop(ParseDelimited(Text, " ", 2))); } Global.SendCommandText(Text); } Begin: if (SystemPage.Link != None && SystemPage.Link != Self) { SystemPage.CloseLink(SystemPage.Link, True); SystemPage.Link = Self; } else if (SystemPage.Link == None) SystemPage.Link = Self; SendBufferedData("USER "$UserIdent$" localhost "$ServerAddress$" :"$FullName$CRLF); SendBufferedData("NICK "$NickName$CRLF); // SetTimer(1, false); } state LoggedIn { function ProcessInput(string Line) { local string Temp, Temp2, Temp3; local bool bAddModifier; local int i; local string Command; Global.ProcessInput(Line); if( SystemPage == None ) return; // log("LoggedIn::ProcessInput >"$Line,'IRC'); Command = ParseDelimited(Line, " ", 2); if(ParseDelimited(Line, " ", 1) == "ERROR") { SystemPage.SystemText(ChopLeft(ParseDelimited(Line, ":", 2, True))); } switch ( Command ) { case "JOIN": Temp = ParseDelimited(Line, ":!", 2); //Log("JOIN:"$Line$" Temp:"$Temp$" NickName:"$NickName); if(Temp ~= NickName) Temp = ""; SystemPage.JoinedChannel(Chop(ParseDelimited(Line, " ", 3)), Temp); break; case "PART": Temp = ParseDelimited(Line, ":!", 2); if(Temp ~= NickName) Temp = ""; SystemPage.PartedChannel(Chop(ParseDelimited(Line, " ", 3)), Temp); break; case "NICK": SystemPage.ChangedNick(ParseDelimited(Line, ":!", 2), Chop(ParseDelimited(Line, " ", 3))); break; case "QUIT": SystemPage.UserQuit(ParseDelimited(Line, ":!", 2), ChopLeft(ParseDelimited(Line, " ", 3, True))); break; case "353": // NAMES Temp2 = ParseDelimited(Line, "#", 2); Temp2 = ParseDelimited(Temp2, " :", 1); Temp = ParseDelimited(Line, ":", 3, True); while(Temp != "") { // Nickname Temp3 = ParseDelimited(Temp, " ", 1); SystemPage.UserInChannel("#"$Temp2, RemoveNickPrefix(Temp3)); if(Left(Temp3, 1) == "@") SystemPage.ChangeOp("#"$Temp2, RemoveNickPrefix(Temp3), True); else if(Left(Temp3, 1) == "%") SystemPage.ChangeHalfOp("#"$Temp2, RemoveNickPrefix(Temp3), True); else if(Left(Temp3, 1) == "+") SystemPage.ChangeVoice("#"$Temp2, RemoveNickPrefix(Temp3), True); Temp = ParseDelimited(Temp, " ", 2, True); } break; case "333": // Channel formed info case "366": // End of NAMES case "331": // RPL_NOTOPIC break; case "332": // RPL_TOPIC Temp = Chop(ParseDelimited(Line, " ", 4)); Temp2 = Chop(ParseDelimited(Line, " ", 5, True)); SystemPage.ChangeTopic(Temp, Temp2); break; case "341": // RPL_INVITING break; case "301": // RPL_AWAY SystemPage.PrintAwayMessage(Chop(ParseDelimited(Line, " ", 4)), ChopLeft(ParseDelimited(Line, ":", 3, True))); break; case "NOTICE": Temp = ParseDelimited(Line, ": ", 2); Temp2 = ParseDelimited(Line, ":! ", 2); if(InStr(Temp, "!") != -1 && InStr(Temp2, ".") == -1) { // it's a Nick. Temp = ChopLeft(ParseDelimited(Line, " ", 4, True)); if(Asc(Left(Temp, 1)) == 1 && Asc(Right(Temp, 1)) == 1) SystemPage.CTCP("", Temp2, Mid(Temp, 1, Len(Temp) - 2)); else SystemPage.UserNotice(Temp2, Temp); } else SystemPage.SystemText(ChopLeft(ParseDelimited(Line, " ", 4, True))); break; case "MODE": // channel mode Temp = Chop(ParseDelimited(Line, " ", 4)); // channel Temp3 = Chop(ParseDelimited(Line, " ", 3)); i = 5; bAddModifier = True; while(Temp != "") { Temp2 = Left(Temp, 1); if(Temp2 == "+") bAddModifier = True; if(Temp2 == "-") bAddModifier = False; if(Temp2 == "o") { SystemPage.ChangeOp(Temp3, Chop(ParseDelimited(Line, " ", i)), bAddModifier); i++; } if(Temp2 == "h") { SystemPage.ChangeHalfOp(Temp3, Chop(ParseDelimited(Line, " ", i)), bAddModifier); i++; } if(Temp2 == "v") { SystemPage.ChangeVoice(Temp3, Chop(ParseDelimited(Line, " ", i)), bAddModifier); i++; } Temp = Mid(Temp, 1); } SystemPage.ChangeMode(Temp3, ParseDelimited(Line, ":!", 2), ChopLeft(ParseDelimited(Line, " ", 4, True))); break; case "KICK": // FIXME: handle multiple kicks in a single message SystemPage.KickUser(Chop(ParseDelimited(Line, " ", 3)), Chop(ParseDelimited(Line, " ", 4)), ParseDelimited(Line, ":!", 2), ChopLeft(ParseDelimited(Line, ":", 3, True))); break; case "INVITE": SystemPage.SystemText(ParseDelimited(Line, ":!", 2)@InviteString@ParseDelimited(Line, ":", 3)); break; case "PRIVMSG": Temp = Chop(ParseDelimited(Line, " ", 3)); Temp2 = ChopLeft(ParseDelimited(Line, " ", 4, True)); if(Mid(Temp2, 1, 7) == "ACTION " && Asc(Left(Temp2, 1))==1 && Asc(Right(Temp2, 1))==1) { Temp2 = Mid(Temp2, 8); Temp2 = Left(Temp2, Len(Temp2) - 1); if(Temp != "" && InStr("&#@", Left(Temp, 1)) != -1) SystemPage.ChannelAction(Temp, ParseDelimited(Line, ":!", 2), Temp2); else SystemPage.PrivateAction(ParseDelimited(Line, ":!", 2), Temp2); } else if(Asc(Left(Temp2, 1))==1 && Asc(Right(Temp2, 1))==1) { Temp2 = Mid(Temp2, 1, Len(Temp2) - 2); switch(Temp2) { case "VERSION": // if _RO_ SendBufferedData("NOTICE "$ParseDelimited(Line, ":!", 2)$" :"$Chr(1)$"VERSION "$VersionString$Level.ROVersion$Chr(1)$CRLF); // else //SendBufferedData("NOTICE "$ParseDelimited(Line, ":!", 2)$" :"$Chr(1)$"VERSION "$VersionString$Level.EngineVersion$Chr(1)$CRLF); SystemPage.CTCP(Temp, ParseDelimited(Line, ":!", 2), Temp2); break; default: SystemPage.CTCP(Temp, ParseDelimited(Line, ":!", 2), Temp2); break; } } else { if(Temp != "" && InStr("&#@", Left(Temp, 1)) != -1) SystemPage.ChannelText(Temp, ParseDelimited(Line, ":!", 2), Temp2); else SystemPage.PrivateText(ParseDelimited(Line, ":!", 2), Temp2); } break; case "TOPIC": Temp = Chop(ParseDelimited(Line, " ", 3)); Temp2 = ChopLeft(ParseDelimited(Line, " ", 4, True)); SystemPage.ChangeTopic(Temp, Temp2); break; case "305": // No longer away case "306": // Now marked as away break; case "475": // ERR_BADCHANNELKEY SystemPage.SystemText(ChopLeft(ParseDelimited(Line, " ", 4, True))); SystemPage.NotifyChannelKey(Chop(ParseDelimited(ChopLeft(ParseDelimited(Line, " ", 4, True)), ":", 1))); break; default: if ( SystemPage.IsDigit(Command) ) SystemPage.SystemText(ChopLeft(ParseDelimited(Line, " ", 4, True))); break; } } Begin: if(DefaultChannel != "") JoinChannel(DefaultChannel); } function JoinChannel(string Channel) { log("UT2K4IRCLink: JoinChannel: "$Channel,'IRC'); if ( Channel == "" ) Channel = SystemPage.GetCurrentChannelName(); if(Left(Channel, 1) != "#") Channel = "#" $ Channel; SendBufferedData("JOIN "$Channel$CRLF); } function PartChannel(string Channel) { if ( Channel == "" ) Channel = SystemPage.GetCurrentChannelName(); if(Left(Channel, 1) != "#") Channel = "#" $ Channel; SendBufferedData("PART "$Channel$CRLF); } function SetNick(string NewNick) { if ( NewNick == "" ) return; if ( NewNick ~= "chanserv" ) return; if ( NewNick ~= "q" ) return; if ( NewNick ~= "nickserv" ) return; SendCommandText("NICK "$NewNick); } function SetAway(string AwayText) { SendBufferedData("AWAY :"$AwayText$CRLF); } function DestroyLink() { SystemPage = None; SetTimer(0.0,False); if(IsConnected()) { SendText("QUIT :"$DisconnectReason$CRLF); Close(); } else Destroy(); } defaultproperties { InvalidAddressText="Invalid server address, aborting." ErrorBindingText="Error binding local port, aborting." TimeOutError="Timeout connecting to server." ResolveFailedText="Failed to resolve server address, aborting." ConnectedText="Connected." ConnectingToText="Connecting to" DisconnectReason="Disconnected" VersionString="Killing Floor IRC Client version " InviteString="invites you to join" Shortcuts(0)=(AliasText="MSG",RealCommand="PRIVMSG") Shortcuts(1)=(AliasText="LEAVE",RealCommand="PART") Shortcuts(2)=(AliasText="J",RealCommand="JOIN") Shortcuts(3)=(AliasText="P",RealCommand="PART") Shortcuts(4)=(AliasText="N",RealCommand="NICK") }