diff --git a/mrc_client.mps b/mrc_client.mps new file mode 100644 index 0000000..b64a1d9 --- /dev/null +++ b/mrc_client.mps @@ -0,0 +1,2017 @@ +/// ::::: __________________________________________________________________ ::::: +// : ____\ ._ ____ _____ __. ____ ___ _______ .__ ______ .__ _____ .__ _. /____ : +// __\ .___! _\__/__ / _|__ / _/_____ __| \ gRK __|_ \ __ |_ \ !___. /__ +// \ ! ___/ |/ /___/ | \__\ ._/ __\/ \ \___/ |/ \/ \_./ \___ ! / +// /__ (___ /\____\____|\ ____| / /___|\ ______. ____\|\ ___) __\ +// /____ \_/ ___________ \_/ __ |__/ _______ \_/ ____ |___/ _____ \_/ ____\ +// : /________________________________________________________________\ : +// ::::: + p H E N O M p R O D U C T I O N S + ::::: +// ============================================================================== +// +// ----------------------------------------- +// - modName: MRC Mystic Client - +// - majorVersion: 1.2 - +// - minorVersion: 9a - +// - author: Stackfault - +// - publisher: Phenom Productions - +// - website: https://www.phenomprod.com - +// - email: stackfault@bottomlessabyss.net - +// - bbs: bbs.bottomlessabyss.net:2023 - +// ----------------------------------------- +// +// Update history +// +// Version 1.1 - Gryphon // Cyberia BBS +// Version 1.1.4 - Stackfault [sf] // The Bottomless Abyss +// Version 1.2.5 - Stackfault [sf] // The Bottomless Abyss +// Version 1.2.7 - Stackfault [sf] // The Bottomless Abyss +// Version 1.2.9 - Stackfault [sf] // The Bottomless Abyss +// Version 1.2.9a - Stackfault [sf] // The Bottomless Abyss +// Version 1.2.9b - MeaTLoTioN [mL] // The Quantum Wormhole +// Version 1.2.9c - MeaTLoTioN [mL] // The Quantum Wormhole +// Version 1.2.9d - MeaTLoTioN [mL] // The Quantum Wormhole +// Version 1.2.9e - MeaTLoTioN [mL] // The Quantum Wormhole +// Version 1.2.9f - MeaTLoTioN [mL] // The Quantum Wormhole +// Version 1.2.9g - MeaTLoTioN [mL] // The Quantum Wormhole +// +// List of changes/fixes: +// +// v1.1.4 +// - Input buffer is now unblocking, you can see received chats while typing +// - Input buffer length is maxed, with color coded character counter +// - Input buffer history still available with UP/DOWN arrow keys +// - Fixed a race condition when line cannot be word wrapped +// - Added CPU release in each loops +// - Visible heartbeat animation when server refresh is active +// - Improved responsiveness and buffer refresh rate +// - Enlarged view port, now using the full 24 lines screen height +// - Now shows connected chatters when logging in +// - Welcome text at connection +// - New /changes command +// - 100% compatible with current MRC server implementation +// - Various small fixes +// - Implemented command whitelisting around pipe codes +// - Implemented colors live switching with PgUp/PgDn +// - Implemented timestamp highlighting on nick mention +// - Various validation of string input and length checking +// - Show private message when sent +// - Changed mention indicator to better support present/absent clock +// - Change wrapping on multiline chats to better use available screen space +// - Added information scroller +// +// v1.2.5 +// - Added message queuing serialization +// - Added multi-line packet handling +// - Updated scrolling ticker behavior +// - Added new server commands support +// - Stale queue indicator (heartbeat change color) +// - User now marked as unavailable for node message when in MRC +// - Increased topic to 55 chars +// - Added file-locking check via exclusive open +// - Tilde character handling +// - Nick autocomplete +// - Fixed and optimized clear screen +// - Many other smaller changes +// - Small issues fixed and code cleanup +// +// v1.2.7 (Hotfix release) +// - Server banner support +// - Several fixes +// - Client/Server latency display +// - Changes scroller/banner behavior +// +// v1.2.9 (Hotfix release) +// - Topic can now contain colon +// - Addressed some bleeding in banners +// - Added server hello/iamhere dialog support +// - Fixed screen update for some calls +// - Fixed issue with userlist server refresh +// - Fixed issue with server-side banners +// - Added server stats in scrolling banner +// - Reply to last received private message using /r +// - Word-wrapping routine redone to support long strings +// - Updated skin to be more intuitive re latency, buffer, etc +// - Redone the formatting of the /SET features display +// - Added missing /SET command to code +// - Converted all loop timing events to prime numbers to avoid collisions +// - Addressed the cause for random crash +// +// v1.2.9a (Hotfix release) +// - Fixed scroller issues with some OS/Architecture combinations +// - Added support for scroller background color in customization block +// - Added an easter surprise +// +// v1.2.9b (Text Themes addition) [mL] +// - Added two new text themes; /kewl /leet +// - /kewl: aBCDeFGHiJKLMNoPQRSTuVWXYZ +// - /leet: 4BCD3FGH1JK1MN0PQR57UVWXYZ +// +// v1.2.9c (Text Themes addition) [mL] +// - Added a new text themes; /jumble +// - /jumble: This basically scrambles each word as if the computer had dyslexia +// +// v1.2.9d (Text Themes addition) [mL] +// - Added a new text themes; /rot13 +// - /rot13: This basically uses the Rot13 cipher to scramble each word or unscramble +// +// v1.2.9e (qUAntUm RaDio! Now and Next display) [mL] +// - Added /qrstat to show what's playing now and next on qUAntUm RaDio! +// +// v1.2.9f (Making a private message more obvious) [mL] +// - When sending a private message using either /m /msg /t or /r the input +// - bar will turn red with white text so you know that you're typing a private +// - message and not a public message. +// +// v1.2.9g (Making a broadcast message more obvious) [mL] +// - When sending a broadcast message using /b the input bar will now turn +// - green with white text so you know that you're typing a broadcast message. +// +// ************************************************************************* +// * Starting with this release, only the current and previous version * +// * of the client will be able to connect to the server, make sure * +// * to keep your installation updated. * +// ************************************************************************* +// +// Installation instructions +// +// MRC Central server has relocated, please use the address below. +// ==================================================================== +// mrc.bottomlessabyss.net on port 5000 +// ==================================================================== +// +// - This release if distributed as a complete installation, including +// both the mrc_client.py and mrc_client.mps. +// - Make sure you update both to benefit all the changes. +// - Some customization variables added, specifically around input bar +// + +Uses Cfg +Uses User + +Const MRCVersion = 'Multi Relay Chat MPL v1.2.9g [mL]' +Const CLBuffer = 25 +Const InputSize = 255 +Const MaxBuffer = 140 // Max input buffer limit [sf] + +Type MRCRec = Record + FromUser : String[30] + FromSite : String[30] + FromRoom : String[30] + ToUser : String[30] + ToSite : String[30] + ToRoom : String[30] + Message : String[InputSize] +End + +Type UserRec = Record + RecIdx : Integer + PermIdx : Integer + EnterChatMe : String[80] + EnterChatRoom : String[80] + EnterRoomMe : String[80] + EnterRoomRoom : String[80] + LeaveChatMe : String[80] + LeaveChatRoom : String[80] + LeaveRoomMe : String[80] + LeaveRoomRoom : String[80] + Name : String[80] + DefaultRoom : String[80] + Temp1 : String[80] + Temp5 : String[80] + Temp6 : String[80] + Temp7 : String[80] + NameColor : String[16] + LtBracket : String[16] + RtBracket : String[16] + UseClock : Boolean + ClockFormat : Boolean + ShowSmilies : Boolean +End + +Var Plyr : UserRec +Var WinTL, WinTT : Byte = 0 +Var WinBL, WinBB : Byte = 0 + +Var ChatLines : Array [1..CLBuffer] of String[160] +Var WinAttr : Byte = 0 +Var WinSize : Integer = 0 +Var PromptX : Byte = 0 +Var PromptY : Byte = 0 +Var PromptAttr : Byte = 0 +Var RoomX, RoomY : Byte = 0 +Var RoomAttr : Byte = 0 +Var TopicX, TopicY : Byte = 0 +Var TopicAttr : Byte = 0 +Var MyNamePrompt : String = '' +Var MyChatRoom : Integer = 1 +Var Loop : Integer = 1 +Var SiteTag : String = '' +Var UserTag : String = '' +Var MyRoom : String = '' +Var MyTopic : String = '' +Var ServFile : MRCRec +Var BBSTempStub : String = '' +Var ChatLog : String = '' +Var PInUse : String = '' +Var Scroller : String = '' +Var UserFile : String = JustPath(Progname)+'mrcusers.dat' +Var ChatSeed : Integer = 0 +Var NodeMsgFlag : Boolean = False +Var BufferHist : Array [1..10] of String [160] // Buffer history [sf] +Var BannerList : Array [1..20] of String [160] // Header banners [sf] +Var BannerOff : Byte = 0 // Banner scrolling offset [sf] +Var BanIdx : Byte = 1 // Banner index [sf] +Var ScrollWait : Byte = 0 // Banner scrolling wait [sf] +Var HBClr : Byte = 11 // Color of Heartbeat [sf] +Var RoomUsers : String = '' // Comma delimited user list [sf] +Var UserIdx : Byte = 1 // Index of UserList search [sf] +Var LastUSearch : String = '' // Last user search string [sf] +Var LastPrivMsg : String = '' // Last private message received [sf] +Var Latency : Integer = 0 // Network latency in ms [sf] +Var MRCStats : String = '' // MRC Stats string [sf] +Var RefreshChat : Boolean = False +Var IMaxBuffer : Byte = MaxBuffer + +// Align this path with the Python client config [sf] +// Default: mystic/data/mrc +Var SvrQueuePath : String = CfgDataPath + 'mrc' + +// +// Beginning of customization variables block [CUST] +// + +Var LatencyColor : String = '' // Default color of latency indicator [ml] +Var RoomUsersColor : String = '' // Default color of roomusers indicator [ml] + +// Heartbeat animation sequence [sf] +Var Heartbeat : String = Chr(176) + Chr(177) + Chr(178) + Chr(219) + Chr(178) + Chr(177) + +Var HeartbeatX : Byte = 76 // X position of HeartBeat [sf] +Var HeartbeatY : Byte = 23 // Y position of HeartBeat [sf] +Var HeartbeatGood : Byte = 10 // Color of HeartBeat when local queue is flowing [sf] +Var HeartbeatBad : Byte = 12 // Color of HeartBeat when local queue is stalled [sf] + +Var LatX : Byte = 12 +Var LatY : Byte = 23 +Var LatCol : Byte = 7 + +Var UsrX : Byte = 29 +Var UsrY : Byte = 23 +Var UsrCol : Byte = 7 + +Var CounterX1 : Byte = 60 // X position of characters counter [sf] +Var CounterY1 : Byte = 23 // X position of characters counter [sf] + +Var CounterX2 : Byte = 64 // X position of max characters count [sf] +Var CounterY2 : Byte = 23 // X position of max characters count [sf] + +//Var InputBg : String = Chr(250) // Input background character [sf] +Var InputBg : String = Chr(32) // Input background character [sf] +Var InputClr : String = '|16|17|07' // Input field color [sf] +Var Cursor : String = Chr(178) // Virtual Cursor Character +Var CursorBg : String = '|25' // Cursor Background color + +//Var CIdx,TIdx : Byte = 7 // Chat text color index [sf] +Var CIdx,TIdx : Byte = 10 // Chat text color index [sf] [ml] + +Var MClr : String = '|24|10' // Mention indicator colors (background+foreground) +Var MChr : String = Chr(175) // Mention indicator character +Var TClr : Byte = 8 // Timestamp color +Var BBg : Byte = 16 // Scroller background (16-23) [sf] +Var BClr : Byte = 11 // Scroller color [sf] +Var BFad1 : Byte = 3 // Scroller fader color 1 [sf] +Var BFad2 : Byte = 8 // Scroller fader color 2 [sf] +Var BannerX : Byte = 43 // XPosition of banner [sf] +Var BannerY : Byte = 2 // YPosition of banner [sf] +Var BannerLen : Byte = 36 // Length of banner [sf] +Var ScrollDly : Byte = 0 // Banner scrolling start delay +Var ScrollSpeed : Byte = 15 // Scroll speed factor (Lower = Faster) + +// Define banner to scroll, loaded at each banner change (for dynamic content for example) +Procedure LoadBanners +Begin + BannerList[1] := MRCVersion + MRCStats + BannerList[2] := 'Find more about the connected BBSes using the /INFO command' + BannerList[3] := 'Give a try to the new nick auto-completion feature using the TAB key' + BannerList[4] := 'Reply to your last received private message using the /R shortcut' + BannerList[5] := 'Wanna have a go at tqwCTF? Ask MeaTLoTioN for more information' + BannerList[6] := 'Thanks to all who join thE grAvY trAIn each Friday, 29 user record! (2022-04-08)' + // SysOp can add their own banners +End + +// +// End of customization variables block [CUST] +// + +Function ReadPlyr(I:Integer):Boolean +Var Ret : Boolean = False +Var Fptr : File +Begin + fAssign(Fptr,UserFile,66) + fReset(Fptr) + If IoResult = 0 Then Begin + fSeek(Fptr,(I-1)*SizeOf(Plyr)) + If Not fEof(fptr) Then Begin + fReadRec(Fptr,Plyr) + Ret:=True + End + fClose(Fptr) + End + ReadPlyr:=Ret +End + +Procedure SavePlyr(I:Integer) +Var Fptr : File +Begin + fAssign(Fptr,Userfile,66) + fReset(Fptr) + If IoResult = 0 Then + fSeek(Fptr,(I-1)*SizeOf(Plyr)) + Else Begin + Plyr.RecIdx:=1 + fReWrite(Fptr) + End + fWriteRec(Fptr,Plyr) + fClose(Fptr) +End + +Function FindPlyr:Integer +Var X,Ret : Integer = 0 +Var Done : Boolean = False +Var UN : String = '' +Begin + X:=1 + UN:=Upper(StripMCI(Replace(UserAlias,' ','_'))) + While ReadPlyr(X) And Not Done Do Begin + If StripMCI(Upper(Plyr.Name)) = UN Then Begin + Done:=True + Ret:=X + End + X:=X+1 + End + FindPlyr:=Ret +End + +Procedure NewPlyr +Var I : Integer = 0 +Begin + I:=0 + While ReadPlyr(I+1) Do I:=I+1 + + Plyr.RecIdx :=I+1 + Plyr.PermIdx :=UserIndex + Plyr.EnterChatMe :='|07- |15You have entered chat' + Plyr.EnterChatRoom :='|07- |11%1 |03has arrived!' + Plyr.LeaveChatMe :='|07- |12You have left chat.' + Plyr.LeaveChatRoom :='|07- |12%1 |04has left chat.' + Plyr.EnterRoomMe :='|07- |11You are now in |02%3' + Plyr.LeaveRoomRoom :='|07- |02%1 |10has left the room.' + Plyr.LeaveRoomMe :='|07- |10You have left room |02%4' + Plyr.EnterRoomRoom :='|07- |11%1 |03has entered the room.' + Plyr.Defaultroom :='lobby' + Plyr.NameColor :='|11' + Plyr.LtBracket :='|03<' + Plyr.RtBracket :='|03>' + Plyr.UseClock :=True + Plyr.ClockFormat :=False + Plyr.ShowSmilies :=False + + Plyr.Name:=StripMCI(Replace(UserAlias,' ','_')) + + SavePlyr(Plyr.RecIdx) +End + +Procedure CleanOut +Var X : Byte = 0 +Begin + For X:=1 To 20 Do BannerList[X] := '' + For X:=1 To 10 Do BufferHist[X] := '' + For X:=1 To CLBuffer Do ChatLines[X] := '' + + FindFirst(CfgTempPath + '*.mrc',66) + While DosError = 0 Do Begin + If FileExist(CfgTempPath+DirName) Then + FileErase(CfgTempPath+DirName) + FindNext + End + FindClose + If FileExist(PInUse) Then + fileErase(PInUse) + If FileExist(ChatLog) Then + fileErase(ChatLog) +End + +// Add Banners from Server [sf] +Procedure AddBanner(Text:String) +Var BanExist : Boolean = False +Begin + BanIdx := 1 + Repeat + If BannerList[BanIdx] = Text Then + BanExist := True + BanIdx := BanIdx + 1 + Until Length(BannerList[BanIdx]) = 0 or BanIdx > 20 + If BanExist = False Then + Begin + If BanIdx < 21 Then + BannerList[BanIdx] := Text + BanIdx := 1 + End +End + +Procedure UpdateScreen +Begin + WriteXY(RoomX,RoomY,RoomAttr,PadRt('#'+MyRoom,30,' ')) + WriteXY(TopicX,TopicY,TopicAttr,PadRt(MyTopic,55,' ')) + WriteXY(LatX, LatY, LatCol, LatencyColor + PadLt(Int2Str(Latency), 3, ' ')) // [ml] + WriteXY(UsrX, UsrY, UsrCol, RoomUsersColor + PadLt(Int2Str(WordCount(RoomUsers,',')), 2, ' ')) // [ml] +End + +Procedure ShowChat(Top:Integer) +Var C,T,L,Y,X : Integer = 0 +Var G,V,W : String = '' +Var N2D : Boolean = True +Begin + If RefreshChat Then + Begin + Y:=CLBuffer-WinSize-Top + Write('|08') + For X:=1 To WinSize+1 Do Begin + GoToXy(1,WinTT+X-1) + Write(ChatLines[Y]+'|16|$X80 ') + Y:=Y+1 + End + RefreshChat := False + End +End + +// Return PIPE code based on byte +Function GetPipe(C:Byte) : String +Begin + If C < 32 Then + GetPipe := '|' + PadLT(Int2Str(C), 2, '0') +End + +// Parse chat into a message record +Function ParseChat(S:String) : MrcREc +Var MR : MrcRec +Begin + MR.FromUser := WordGet(1,S,'~') + MR.FromSite := WordGet(2,S,'~') + MR.FromRoom := WordGet(3,S,'~') + MR.ToUser := WordGet(4,S,'~') + MR.ToSite := WordGet(5,S,'~') + MR.ToRoom := WordGet(6,S,'~') + MR.Message := WordGet(7,S,'~') + ParseChat := MR +End + +Procedure CheckUserlist(U:String) +Var X : Integer = 0 +Var A : Boolean = True +Begin + For X := 1 to WordCount(RoomUsers, ',') Do + If Upper(U) = Upper(WordGet(X, RoomUsers, ',')) Then + A := False + + If U = 'SERVER' or U = 'CLIENT' Then + A := False + + If A = True Then + Begin + If Length(RoomUsers) > 0 Then + RoomUsers := RoomUsers + ',' + U + Else + RoomUsers := U + End +End + +Procedure RedrawScreen +Begin + DispFile('mrcmain') + UpdateScreen + RefreshChat:=True + ShowChat(0) +End + +// Cut string based on available space +// Word-wrap or string-cut based on the best option +Function StringCutter(S:String;L:Byte):Byte +Var P,W,O : Byte = 0 +Var S1,S2 : String = '' +Var Done : Boolean = False +Begin + P := Length(S) + Repeat + S1 := Copy(S,1,P) + If Length(StripMCI(S1)) < L Then + Begin + O := WordCount(S1, ' ') + W := WordPos(O, S1, ' ') + + If Length(StripMCI(S)) < L Or W < P - 10 Then + W := 0 + + If W > 0 Then + Begin + StringCutter := W - 1 + Done := True + End + Else + Begin + StringCutter := P + Done := True + End + End + P := P-1 + Until Done = True +End + +Procedure Add2Chat(S:String) +Var E,W,L,B,A,X : Integer = 0 +Var DS,S1,S2,S3,S4 : String = '' +Var HL : String = '' +Var ALL : String = '@ROOM' +Var SM1,SM2,SM3 : Integer +Begin + // Try and replace a :) or :D with a  or  + If Plyr.ShowSmilies Then + Begin + While Pos(':)', S) > 0 Do + Begin + SM1:=Pos(':)', S) + SM2:=SM1+1 + Delete(S,SM2,1) + S[SM1]:=Chr(1) + End + While Pos(':D', S) > 0 Do + Begin + SM1:=Pos(':D', S) + SM2:=SM1+1 + Delete(S,SM2,1) + S[SM1]:=Chr(2) + End + While Pos('=)', S) > 0 Do + Begin + SM1:=Pos('=)', S) + SM2:=SM1+1 + Delete(S,SM2,1) + S[SM1]:=Chr(2) + End + End + + // Highlight on nick mention [sf] + HL := '|16|00.' + If Pos(Upper(UserTag), Upper(StripMCI(Copy(S, Length(WordGet(1,S,' ')), Length(S))))) > 0 Then + Begin + Write('|[X02|[Y02|16|00|BE|07') + HL := MClr + MChr + '|16|07' + End + + If Pos(Upper(ALL), Upper(StripMCI(Copy(S, Length(WordGet(1,S,' ')), Length(S))))) > 0 Then + Begin + Write('|[X02|[Y02|16|00|BE|07') + HL := MClr + MChr + '|16|07' + End + + S3:=' ' + Chr(28) + ' ' + If Plyr.UseClock Then Begin + Begin + DS:=TimeStr(DateTime,Plyr.ClockFormat) + If Not Plyr.ClockFormat Then + Delete(DS,6,3) + S:=GetPipe(TClr)+DS+HL+'|16|07'+S+'|16' + S3:=StrRep(' ', Length(DS)) + S3 + End + Else + S:=HL+'|16|07'+S + + S1:=S + Repeat + Delay(5) + + W := StringCutter(S1,79) + + If Length(S1) > W Then + Begin + S2 := Copy(S1, W+1, Length(S1)) + S4 := Copy(S1, 1, W) + S1 := S4 + End + Else + S2 := '' + + If (S1 <> '') Then + Begin + For X:=2 To CLBuffer Do + ChatLines[X-1]:=ChatLines[X] + ChatLines[CLBuffer]:='|16'+S1 + AppendText(ChatLog,ChatLines[CLBuffer]) + RefreshChat := True + S1:=S3+S2 + End + + Until S2='' +End + +// Check if stale outbound messages are stuck in queue +Procedure CheckStale +Begin + HBClr := HeartbeatGood + FindFirst(SvrQueuePath + PathChar + '*.mrc', 63) + While DOSError = 0 Do + Begin + If DateTime > DirTime Then + HBClr := HeartbeatBad + FindNext + End + FindClose +End + +Procedure MakeChatEntry(S:String) +Var Fil : String = SvrQueuePath + PathChar + + Int2Str(NodeNum) + Int2Str(ChatSeed) + + Int2Str(Random(9))+Int2Str(Random(9)) + '.mrc' +Begin + AppendText(Fil,S) + ChatSeed:=ChatSeed+1 +End + +Procedure SendOut(FU,FS,FR,TU,TS,TR,S:String) +Var TX : String = '' +Begin + TX:=FU+'~'+FS+'~'+FR+'~'+TU+'~'+TS+'~'+TR+'~'+S+'~' + MakeChatEntry(TX) +End + +Procedure SendToMe(S:String) +Begin + Add2Chat(S) + ShowChat(0) +End + +Procedure SendToAllNotMe(S:String) +Begin + SendOut(UserTag,SiteTag,MyRoom,'NOTME','','',S) +End + +Procedure SendToRoomNotMe(S:String) +Begin + SendOut(UserTag,SiteTag,MyRoom,'NOTME','',MyRoom,S) +End + +Procedure SendToAll(S:String) +Begin + SendOut(UserTag,SiteTag,MyRoom,'','','',S) +End + +Procedure SendToRoom(S:String) +Begin + If Pos('@ROOM',Upper(S)) > 0 Then + SendOut(UserTag,SiteTag,MyRoom,'','',MyRoom,'|21|15'+S) + Else + SendOut(UserTag,SiteTag,MyRoom,'','',MyRoom,S) +End + +Procedure SendToUser(U,S:String) +Begin + SendOut(UserTag,SiteTag,MyRoom,U,'','',S) +End + +Procedure SendToClient(S:String) +Begin + SendOut(UserTag,SiteTag,MyRoom,'CLIENT',SiteTag,MyRoom,S) +End + +Procedure SendToServer(S:String) +Var SS : String +Begin + SendOut(UserTag,SiteTag,MyRoom,'SERVER',SiteTag,MyRoom,S) +End + +Procedure ProcessChat(MR:MRCRec) +Var Ok2Send : Boolean = True +Var Command,Opt1,Opt2,Stats : String = '' +Begin + + // Handle topic set from server + If Mr.FromUser = 'SERVER' and + Pos('ROOMTOPIC',Mr.Message) > 0 Then + Begin + Ok2Send := False + Command:=WordGet(1,Mr.Message,':') + opt1:=WordGet(2,Mr.Message,':') + opt2:=Copy(Mr.Message, + WordPos(3,Mr.Message,':'), + Length(Mr.Message)-WordPos(3,Mr.Message,':')+1) + If Opt1 = MyRoom Then Begin + MyTopic:=Opt2 + UpdateScreen + End + End + + // Handle userlist from server + If Mr.FromUser = 'SERVER' and + Pos('USERLIST',Mr.Message) > 0 Then + Begin + Ok2Send := False + Command := WordGet(1,Mr.Message,':') + opt1 := WordGet(2,Mr.Message,':') + RoomUsers := opt1 + WriteXY(UsrX, UsrY, UsrCol, RoomUsersColor + PadLt(Int2Str(WordCount(RoomUsers,',')), 2, ' ')) // [ml] + End + + // Handle client hello request + If Mr.FromUser = 'SERVER' and + Mr.Message = 'HELLO' Then + Begin + Ok2Send := False + SendToServer('IAMHERE') + End + + // Handle Latency from client + If Mr.FromUser = 'SERVER' and + Pos('LATENCY',Mr.Message) > 0 Then + Begin + Ok2Send := False + Latency := Str2Int(WordGet(2,Mr.Message,':')) + If Latency > 999 Then + Latency := 999 + If Latency > 250 Then // [ml] + LatencyColor := '' // [ml] + Else // [ml] + LatencyColor := '' // [ml] + WriteXY(LatX, LatY, LatCol, LatencyColor + PadLt(Int2Str(Latency), 3, ' ')) // [ml] + End + + // Handle stats from client + If Mr.FromUser = 'SERVER' and + Pos('STATS',Mr.Message) > 0 Then + Begin + Ok2Send := False + Stats := WordGet(2,Mr.Message,':') + If Length(Stats) > 0 Then + Begin + MRCStats := ' :: Server Stats >> BBSes:' + WordGet(1, Stats, ' ') + + ' Rooms:' + WordGet(2, Stats, ' ') + + ' Users:' + WordGet(3, Stats, ' ') + LoadBanners + End + End + + // Handle Banners from server + If Mr.FromUser = 'SERVER' and + Pos('BANNER',Mr.Message) > 0 Then + Begin + Ok2Send := False + AddBanner(WordGet(2,Mr.Message,':')) + End + + // Message for another room + If MR.ToRoom <> '' Then + if Upper(MR.ToRoom) <> Upper(MyRoom) Then + Ok2Send:=False + + // Message not empty and not for me + If MR.ToUser <> '' Then + If Mr.ToUser <> 'NOTME' Then + If Pos(Upper(MR.ToUser),Upper(UserTag))=0 + Then Ok2Send:=False + Else + If Mr.ToUser <> 'NOTME' Then + If Upper(Mr.FromUser) = Upper(UserTag) Then + Ok2Send:=False + + If Upper(Mr.FromUser) = Upper(UserTag) and + Mr.ToUser = 'NOTME' Then + Ok2Send:=False + + If Mr.FromUser <> 'SERVER' and Mr.FromUser <> 'CLIENT' Then + If Upper(Mr.ToUser) = Upper(UserTag) Then + LastPrivMsg := Mr.FromUser + + If Ok2Send Then + Begin + Add2Chat(MR.Message) + CheckUserlist(MR.FromUser) + End +End + +Procedure ReadChatFiles +Var F1 : File +Var S : String = '' +Var Ret : Boolean = False +Var TLines : Array [1..100] of String [255] +Var TSorted : Array [1..100] of String [255] +Var TStamp : LongInt = 0 +Var LCount : Byte = 0 +Var Largest : LongInt = 0 +Var LIndex : Byte = 0 +Var A,B,F,L : Byte = 0 +Begin + FindFirst(CfgTempPath+'*.mrc',66) + While DOSError = 0 Do Begin + Ret:=True + fAssign(F1,CfgTempPath+DirName,66) + fReset(F1) + L:=0 + While Not fEof(F1) Do Begin + fReadLn(F1,S) + LCount:=LCount+1 + TStamp := Str2Int(Copy(DirName, 1, 8)) + L + TLines[LCount] := Int2Str(TStamp) + ' ' + S + L:=L+1 + End + fClose(F1) + fileErase(CfgTempPath+DirName) + FindNext + End + FindClose + + // Little home-made sorting routine + // Sort loop 1 - Populate array from largest to lowest + For A:=LCount DownTo 1 Do + Begin + // Sort loop 2 - Read original array from + Largest := 0 + + For B:=1 to LCount Do + Begin + If Str2Int(WordGet(1, TLines[B], ' ')) > Largest Then + Begin + Largest := Str2Int(WordGet(1, TLines[B], ' ')) + LIndex := B + End + End + TSorted[A] := Copy(TLines[LIndex], + WordPos(2, TLines[LIndex], ' '), + Length(TLines[LIndex]) - WordPos(2, TLines[LIndex], ' ') + 1) + TLines[LIndex] := '0' + End + + // Process messages from the sorted array + For F:=1 to LCount Do + Begin + If WordCount(TSorted[F], '~') > 6 Then + Begin + ServFile:=ParseChat(TSorted[F]) + ProcessChat(ServFile) + End + End + + If Ret Then ShowChat(0) +End + +Function UpdateStrings(S,M,U,NR,OR:String):String +Begin + S:=Replace(S,'%1',M) + S:=Replace(S,'%2',U) + S:=Replace(S,'%3','#'+NR) + S:=Replace(S,'%4','#'+OR) + UpdateStrings:=S +End + +// Display error message to chat window [sf] +Procedure ShowError(S:String) +Begin + Add2Chat('|15!|12 ' + S) + ShowChat(0) +End + +Procedure JoinRoom(S:String;B:Boolean) +Var NewRoom,OldRoom : String = '' +Begin + If Length(S) > 30 Then + ShowError('Room name is limited to 30 chars max') + Else Begin + If Length(S) > 0 Then Begin + OldRoom:=MyRoom + NewRoom:=lower(S) + StripB(S,'#') + SendToServer('NEWROOM:'+MyRoom+':'+S) + If B Then Begin + SendToMe(UpdateStrings(Plyr.LeaveRoomMe,Plyr.Name,'',NewRoom,OldRoom)) + SendToRoomNotMe(UpdateStrings(Plyr.LeaveRoomRoom,Plyr.Name,'',NewRoom,OldRoom)) + MyRoom:=NewRoom + SendToMe(UpdateStrings(Plyr.EnterRoomMe,Plyr.Name,'',NewRoom,OldRoom)) + SendToRoomNotMe(UpdateStrings(Plyr.EnterRoomRoom,Plyr.Name,'',NewRoom,OldRoom)) + End + MyRoom:=S + SetPromptInfo(4,'#'+S) + UpdateScreen + SendToServer('USERLIST') + End + End +End + +Procedure ChangeNick(LRNC,N:String;Announce:Boolean) +Var ON : String = '' +Begin + + Case LRNC Of +// 'N': Plyr.Name:=StripMCI(N) + + // Limit left bracket to 1 visible character [sf] + 'L': Begin + If Length(StripMCI(N)) > 1 Then + ShowError('Left bracket max length is 1 char') + Else + Plyr.LtBracket:=N + End + + // Limit right bracket to 8 visible character [sf] + // Record length stays at 16 for compatibility + 'R': Begin + If Length(StripMCI(N)) > 8 or Length(N) > 16 Then + ShowError('Right brackets max length is 8 chars (16 including Pipe codes)') + Else + Plyr.RtBracket:=N + End + + // Make sure Nick color is a color PIPE code [sf] + 'C': Begin + If Length(StripMCI(N)) > 0 or Length(N) <> 3 Then + ShowError('Only color pipe codes allowed for nick color') + Else + Plyr.NameColor:=N + End + End + + SavePlyr(Plyr.RecIdx) + MyNamePrompt:=Plyr.LtBracket+Plyr.NameColor+StripMCI(Plyr.Name)+Plyr.RtBracket+'|16|07 ' +End + +Procedure Init +Var X,Y : Integer = 0 +Var K,S : String = '' +Begin + S:=Int2Str(NodeNum) + For X:=1 To 3 Do + S:=S+Int2Str(Random(9)) + ChatSeed:=Str2Int(S) + ChatLog:=CfgTempPath+'mrcchat.log' + PInUse:=CfgTempPath+'tchat.inuse' + + If Upper(UserAlias) = 'SERVER' or + Upper(UserAlias) = 'CLIENT' or + Upper(UserAlias) = 'NOTME' Then + Begin + WriteLn('|16|12|CL|CRUnfortunately, your User Alias is a reserved word and therefore cannot be used.') + WriteLn('|12Please ask your SysOp to change your User Alias to use MRC.') + WriteLn('|CR|PA|07|CL') + Halt + End + + BBSTempStub:=CfgTempPath + Y:=Pos(Int2Str(NodeNum),BBSTempStub) + If Y > 0 Then + Delete(BBSTempStub,Y,Length(Int2Str(NodeNum))+1) + + Y:=FindPlyr + If Y = 0 Then NewPlyr + Else ReadPlyr(Y) + + SiteTag:=StripMCI(Replace(MCI2Str('BN'),' ','_')) + UserTag:=StripMCI(Replace(UserAlias,' ','_')) + ChangeNick('N',UserTag,False) + DispFile('mrcmain') + GetScreenInfo(1,WinTL,WinTT,WinAttr) + GetScreenInfo(2,WinBL,WinBB,WinAttr) + GetScreenInfo(3,PromptX,PromptY,PromptAttr) + GetScreenInfo(4,RoomX,RoomY,RoomAttr) + GetScreenInfo(5,TopicX,TopicY,TopicAttr) + WinSize:=WinBB-WinTT + + AppendText(PInUse,'0') + MenuCmd('NA','mUltI rElAY chAttIng') +End + +Procedure DoHelp +Begin + Write('|16|11') + DispFile('mrchelp') + RedrawScreen +End + +Procedure DoWho +Begin + Write('|16|11') + MenuCmd('NW','') + RedrawScreen +End + +Procedure ChangeTopic(S:String) +Var R : String = '' +Begin + if Length(S) > 55 Then + ShowError('Topic is limited to 55 chars max') + Else Begin + SendToServer('NEWTOPIC:'+MyRoom+':'+S) + UpdateScreen + End +End + +Procedure DoPrivateMsg(S:String) +Var M,U : String = '' +Var L : Integer = 0 +Begin + Var WC : Byte = WordCount(S, ' ') + if WC > 2 Then + Begin + U:=Upper(WordGet(2,S,' ')) + L:=Pos(U,Upper(S)) + L:=L+Length(U)+1 + M:='|15* |08(|15'+Plyr.Name+'|08/|14PrivMsg|08) |07'+Copy(S,L,Length(S)-L+1) + SendToUser(U,M) + Add2Chat('|15* |08(|14PrivMsg|08->|15' + U + '|08) '+ GetPipe(CIdx) + Copy(S,L,Length(S)-L+1)) + ShowChat(0) + End +End + +Procedure DoBroadcast(S:String) +Var M : String = '' +Begin + M:='|15* |08(|15'+Plyr.Name+'|08/|14Broadcast|08) |07'+Copy(S,4,Length(S)-3) + SendToAll(M) +End + +Procedure DoMeAction(S:String) +Var R : String = '' +Begin + R:=Copy(S,5,Length(S)-4) + SendToRoom('|15* |13'+Plyr.Name+' ' + R) +End + +Procedure DoTroutAction(S:String) +Var R : String = '' +Var Trout : String = '' +Var Tweet : String = '' +Begin + Trout:='|12<''),))>< |15whaaaPish|07' + R:=Copy(S,8,Length(S)-4) + SendToRoom('|15* |13'+Plyr.Name+' |14troutslaps |13'+R+': '+Trout) + Tweet:='/mystic/scripts/tweetme/twitter.py "'+Plyr.Name+' troutslaps '+R+': <''),))>< whaaaPish #retrocomputing #bbs #mrc #troutslap #sysop #mysticbbs #tqwNet"' + MenuCmd('DD', Tweet); +End + + +// Buffer history handling [sf] +Procedure AddToBufferHistory(B:String) +Var I : Byte = 0 +Begin + For I := 10 DownTo 2 Do + Begin + If Length(BufferHist[I-1]) > 0 Then + BufferHist[I] := BufferHist[I-1] + End + BufferHist[2] := B +End + +// Select next banner from the defined list [sf] +Procedure NextBanner +Begin + Repeat + BanIdx := BanIdx + 1 + If BanIdx > 20 Then BanIdx := 1 + Until Length(BannerList[BanIdx]) > 0 + ScrollWait := 0 + BannerOff := 0 +End + +// Display and scroll banners [sf] +Function ScrollBanner +Var BS: String = StripMCI(BannerList[BanIdx]) +Begin + // This is a scrolling banner + + // Add white padding for nice scroll entry/exit + BS:=StrRep(' ', BannerLen) + BS + StrRep(' ', BannerLen) + + // Initial display before we start scrolling + If ScrollWait = 0 Then + Begin + BS:=GetPipe(BBg)+GetPipe(BClr) + Copy(BS, 1, BannerLen-2) + + GetPipe(BFad1) + Copy(BS, BannerLen-1, 1) + + GetPipe(BFad2) + Copy(BS, BannerLen, 1) + '|16' + BannerOff := BannerOff + 1 + GoToXy(BannerX, BannerY) + Write(BS) + GoToXy(HeartBeatX, HeartBeatY) + End + + // We have made it to the end + If BannerOff > Length(BS) - BannerLen Then + Begin + BS:=GetPipe(BBg)+GetPipe(BFad2) + Copy(BS, BannerOff, 1) + + GetPipe(BFad1) + Copy(BS, BannerOff+1, 1) + + GetPipe(BClr) + Copy(BS, BannerOff+2, BannerLen-2) + '|16' + GoToXy(BannerX, BannerY) + Write(BS) + GoToXy(HeartBeatX, HeartBeatY) + ScrollWait := 0 + NextBanner + End + + // Let's start the scrolling shall we + If ScrollWait > ScrollDly Then + Begin + BS:=GetPipe(BBg)+GetPipe(BFad2) + Copy(BS, BannerOff, 1) + + GetPipe(BFad1) + Copy(BS, BannerOff+1, 1) + + GetPipe(BClr) + Copy(BS, BannerOff+2, BannerLen-4) + + GetPipe(BFad1) + Copy(BS, BannerOff+BannerLen-2, 1) + + GetPipe(BFad2) + Copy(BS, BannerOff+BannerLen-1, 1) + '|16' + BannerOff := BannerOff + 1 + GoToXy(BannerX, BannerY) + Write(BS) + GoToXy(HeartBeatX, HeartBeatY) + End + + // Not yet + Else + Begin + ScrollWait := ScrollWait + 1 + GoToXy(HeartBeatX, HeartBeatY) + End +End + +// Buffer history seeker [sf] +Function GetBufferIndex(Idx:Byte;Dir:Integer) : Byte +Var NIdx: Byte = 0 +Begin + NIdx := Idx + Dir + GetBufferIndex := Idx + + If NIdx > 1 and NIdx < 10 and Length(BufferHist[NIdx]) > 0 Then + GetBufferIndex := NIdx + + If NIdx = 1 Then + GetBufferIndex := NIdx +End + +// Color Index seeker [sf] +Function GetColorIndex(Idx:Byte;Dir:Integer) : Byte +Var MIdx: Byte = 0 +Begin + MIdx := Idx + Dir + + // Span from Blue... + If MIdx < 1 Then + MIdx := 1 + + // ... to Bright White + If MIdx > 15 Then + MIdx := 15 + + GetColorIndex := MIdx +End + +Function RainbowString(InString:String) : String +Var PlString : String = '' +Var RbString : String = '' +Var PlStrPos : Byte = 0 +Var PlStrLen : Byte = 0 +Var X : Byte = 0 +Begin + PlStrPos := WordPos(2, InString, ' ') + PlStrLen := Length(InString) - (PlStrPos-1) + PlString := Copy(InString, PlStrPos, PlStrLen) + For X:=1 To PlStrLen Do + RbString := RbString + GetPipe(Random(14)+1) + Copy(PlString, X, 1) + + RainbowString := RbString +End + +//Kewl Speak by [mL] +Function KewlString(InString:String): String +Var PlString : String = '' +Var KlString : String = '' +Var PlStrPos : Byte = 0 +Var PlStrLen : Byte = 0 +Var X : Byte = 0 +Begin + PlStrPos := WordPos(2, InString, ' ') + PlStrLen := Length(InString) - (PlStrPos-1) + PlString := Copy(InString, PlStrPos, PlStrLen) + For X:=1 to PlStrLen Do + Begin + Case Upper(PlString[X]) Of + 'A','E','I','O','U': PlString[X] := Lower(PlString[X]) + Else + PlString[X] := Upper(PlString[X]) + End + KlString := KlString + Copy(PlString, X, 1) + End + + KewlString := KlString +End + +// Leet Speak by [mL] +Function LeetString(InString:String): String +Var PlString : String = '' +Var LtString : String = '' +Var PlStrPos : Byte = 0 +Var PlStrLen : Byte = 0 +Var X : Byte = 0 +Begin + PlStrPos := WordPos(2, InString, ' ') + PlStrLen := Length(InString) - (PlStrPos-1) + PlString := Copy(InString, PlStrPos, PlStrLen) + For X:=1 to PlStrLen Do + Begin + Case Upper(PlString[X]) Of + 'A': PlString[X] := '4' + 'E': PlString[X] := '3' + 'I': PlString[X] := '1' + 'L': PlString[X] := '1' + 'O': PlString[X] := '0' + 'S': PlString[X] := '5' + 'T': PlString[X] := '7' + Else + PlString[X] := Upper(PlString[X]) + End + LtString := LtString + Copy(PlString, X, 1) + End + + LeetString := LtString +End + +//Jumble Speak by [mL] +Function JumbleString(InString:String): String +Var PlString : String = '' +Var JlString : String = '' +Var Tweet : String = '' +Var PlStrPos : Byte = 0 +Var PlStrLen : Byte = 0 +Var X : Byte = 0 +Var Y : Byte = 0 +Begin + Var WC : Byte = WordCount(InString, ' ') + For X:=2 to WC Do + Begin + Var WG : String = WordGet(X, InString, ' ') + Var WL : Byte = Length(WG) + Var TmpString : String = '' + If WL > 3 Then + Begin + TmpString:=WG[1] + For Y:= WL-1 downto 2 Do + Begin + TmpString:=TmpString+WG[Y] + End + TmpString:=TmpString+WG[WL] + End + Else + Begin + TmpString:=WG + End + JlString:=JlString+TmpString + Var JC : Byte = WordCount(JlString, ' ') + If WC-1 > JC Then + JlString:=JlString+' ' + End + + JumbleString := JlString + Tweet := '/mystic/scripts/tweetme/twitter.py "'+Plyr.Name+' just jumbled their words in MRC: '+JlString+' #bbs #mrc #jumble #mysticbbs #tqwNet"' + //MenuCmd('DD', Tweet); + +End + +// Rot13 String by [mL] +Function Rot13String(InString:String): String +Var X : Byte = 0; +Var Y : Byte = 0; +Var NormL : String = 'abcdefghijklmnopqrstuvwxyz'; +Var RotL : String = 'nopqrstuvwxyzabcdefghijklm'; + +Begin + Var WC : Byte = WordCount(InString, ' ') + Var TmpString : String = '' + For X := 2 to WC Do + Begin + Var WG : String = WordGet(X, InString, ' ') + Var WL : Byte = Length(WG) + Var TmpChr : String + Var TmpPos : Byte = 0; + For Y := 1 to WL Do + Begin + TmpChr := Lower(WG[Y]) + TmpPos := Pos(TmpChr, NormL) + If TmpPos > 0 Then + TmpString := TmpString+RotL[TmpPos] + Else + TmpString := TmpString+WG[Y] + End + If X < WC Then + TmpString := TmpString+' ' + End + Rot13String := TmpString +End + +Function InputLine:String +Var IX,UL : Integer = 0 +Var Ch : Char = #13 +Var IBuf : String = '' // Input buffer [sf] +Var DBuf : String = '' // Displayed buffer [sf] +Var RBuf : Boolean = False // Refresh buffer flag [sf] +Var Done : Boolean = False // Done getting input (Enter) [sf] +Var Anim : Byte = 1 // Animation step [sf] +Var BIdx : Byte = 1 // Buffer Current Idx [sf] +Var NIdx : Byte = 1 // Buffer Target Idx [sf] +Var CClr : Byte = 7 // Char counter color [sf] +Var Words : String = '' +Var LastW : String = '' +Var PrefixLen : Byte = 0 +Var SM1,SM2,SM3 : Byte +Begin + UL:=Length(StripMCI(MyNamePrompt)) + IX:=PromptX+Length(StripMCI(MyNamePrompt)) + + // Derive the maximum allowed buffer bnased on packet fields + PrefixLen := Length(UserTag) + Length(SiteTag) + Length(MyRoom)*2 + Length(MyNamePrompt) + 20 + + // Init the characters counter [sf] + WriteXY(CounterX1, CounterY1, 7, PadLt(Int2Str(Length(IBuf)), 3, '0')) + WriteXY(CounterX2, CounterY2, 7, PadLt(Int2Str(IMaxBuffer), 3, '0')) + + // Init the buffer input bar [sf] + GoToXy(PromptX,PromptY) + Write('|16' + MyNamePrompt + InputClr + GetPipe(CIdx) + DBuf + + CursorBg + Cursor + InputClr + '|$X78' + InputBg) + + Repeat + While Not Keypressed Do + Begin + // Improved polling time to keyboard [sf] + Delay(10) + + // Read chat files every 7 cycles [sf] + // Slightly improve file access rate + If Loop % 7 = 0 Then + ReadChatFiles + + // Read chatroom userlist [sf] + If Loop % 1999 = 0 Then + SendToServer('USERLIST') + + // Heartbeat to server every 12000 cycles + // Maintain server heartbeat rate + If Loop % 11987 = 0 Then Begin + SendToServer('IAMHERE') + End + + // Also re-read banners from server and add new ones + If Loop > 47948 Then Begin + SendToServer('BANNERS') + Loop:=1 + End + + If Loop % ScrollSpeed = 0 Then + ScrollBanner + + If Loop % 499 = 0 Then + CheckStale + + If Loop % 997 = 0 Then + Begin + If HBClr = HeartbeatGood Then + SendToClient('LATENCY') + End + + // Animate the heartbeat animation every 53 cycles [sf] + If Loop % 53 = 0 Then + Begin + Anim := Anim + 1 + If Anim > Length(HeartBeat) Then + Anim := 1 + WriteXY(HeartBeatX, HeartBeatY, HBClr, Copy(HeartBeat, Anim, 1)) + GoToXy(HeartBeatX, HeartBeatY) + End + + Loop:=Loop+1 + End + + // Handle arrow keys [sf] + AllowArrow := True + Ch := ReadKey + + // Buffer history handling [sf] + If IsArrow Then + Begin + Case Ch Of + Chr(72) : NIdx := GetBufferIndex(BIdx, 1) // Scroll back input (Up) + Chr(80) : NIdx := GetBufferIndex(BIdx, -1) // Scroll forward input (Dn) + Chr(81) : TIdx := GetColorIndex(CIdx, -1) // Decrease color index (PgDn) + Chr(73) : TIdx := GetColorIndex(CIdx, 1) // Increase color index (PnUp) + Else + NIdx := BIdx + TIdx := CIdx + End + + Ch := Chr(0) + + If BIdx <> NIdx Then + Begin + IBuf := BufferHist[NIdx] + BIdx := NIdx + RBuf := True + End + Else + If CIdx <> TIdx Then + Begin + CIdx := TIdx + RBuf := True + End + End + + // PIPE codes whitelist [sf] + If Copy(IBuf, Length(IBuf), 1) = '|' Then + Begin + Case Ch Of + '0', + '1', + '2', + '3', + '|', + Chr(32), + Chr(8), + Chr(27) : Ch := Ch + Else + Ch := Chr(0) + End + End + + If Ch = Chr(32) and Length(IBuf) < 1 Then + Ch := Chr(0) + + // Nick auto-completion [sf] + If Ch = Chr(9) Then + Begin + Var WC : Byte = WordCount(IBuf, ' ') // Count of words in buffer + Var LW : String = WordGet(WC, IBuf, ' ') // Last word in buffer + Var WL : Byte = Length(LW) // Length of the last word in buffer + + // Define display if at the beginning or mid-sentence + Var Tail : String = '' + If WC < 2 Then + Tail := ': ' + Else + Tail := ' ' + + If LastUSearch = '' Then + LastUSearch := LW + + If WL > 0 and Length(RoomUsers) > 0 Then + Begin + Var PF :String = Copy(IBuf, 1, WordPos(WC, IBuf, ' ')-1) // Buffer prefix + Var UMatch : Boolean = False + Var SLoop : Byte = 0 + + While Not UMatch Do + Begin + If UserIdx > WordCount(RoomUsers, ',') Then UserIdx := 1 + Var UHandle : String = WordGet(UserIdx, RoomUsers, ',') + If Length(UHandle) > 0 and + Upper(LastUSearch) = Upper(Copy(UHandle, 1, Length(LastUSearch))) Then + Begin + UMatch := True + IBuf := PF + UHandle + Tail + End + UserIdx:=UserIdx+1 + SLoop:=SLoop+1 + If SLoop > WordCount(RoomUsers, ',') Then UMatch := True + End + End + End + Else + Begin + LastUSearch := '' + UserIdx := 1 + End + + IMaxBuffer := 255 - PrefixLen + If IMaxBuffer > MaxBuffer Then IMaxBuffer := MaxBuffer + + If Lower(WordGet(1, IBuf, ' ')) = '/rainbow' Then + IMaxBuffer := Abs(IMaxBuffer/4) - 4 + + If Lower(WordGet(1, IBuf, ' ')) = '/t' Then + Begin + CIdx := 15 + TIdx := 15 + CursorBg := '|28' + InputClr := '|20|15|07' + End + Else If Lower(WordGet(1, IBuf, ' ')) = '/r' Then + Begin + CIdx := 15 + TIdx := 15 + CursorBg := '|28' + InputClr := '|20|15|07' + End + Else If Lower(WordGet(1, IBuf, ' ')) = '/m' Then + Begin + CIdx := 15 + TIdx := 15 + CursorBg := '|28' + InputClr := '|20|15|07' + End + Else If Lower(WordGet(1, IBuf, ' ')) = '/msg' Then + Begin + CIdx := 15 + TIdx := 15 + CursorBg := '|28' + InputClr := '|20|15|07' + End + Else If Lower(WordGet(1, IBuf, ' ')) = '/b' Then + Begin + CIdx := 15 + TIdx := 15 + CursorBg := '|26' + InputClr := '|18|15|07' + End + Else + Begin + //CIdx := 10 + //TIdx := 10 + CursorBg := '|25' + InputClr := '|16|17|07' + End + + // Send to server and add to buffer history [sf] + If Ch = Chr(13) Then + Begin + + // Try and replace a :) or :D with a  or  + If Plyr.ShowSmilies Then + Begin + While Pos(':)', IBuf) > 0 Do + Begin + SM1:=Pos(':)', IBuf) + SM2:=SM1+1 + Delete(IBuf,SM2,1) + IBuf[SM1]:=Chr(1) + End + While Pos(':D', IBuf) > 0 Do + Begin + SM1:=Pos(':D', IBuf) + SM2:=SM1+1 + Delete(IBuf,SM2,1) + IBuf[SM1]:=Chr(2) + End + While Pos('=)', IBuf) > 0 Do + Begin + SM1:=Pos('=)', IBuf) + SM2:=SM1+1 + Delete(IBuf,SM2,1) + IBuf[SM1]:=Chr(2) + End + End + + If Lower(WordGet(1, IBuf, ' ')) = '/rainbow' Then + InputLine := RainbowString(IBuf) + Else If Lower(WordGet(1, IBuf, ' ')) = '/kewl' Then + InputLine := KewlString(IBuf) + Else If Lower(WordGet(1, IBuf, ' ')) = '/leet' Then + InputLine := LeetString(IBuf) + Else If Lower(WordGet(1, IBuf, ' ')) = '/jumble' Then + InputLine := JumbleString(IBuf) + Else If Lower(WordGet(1, IBuf, ' ')) = '/rot13' Then + InputLine := Rot13String(IBuf) + Else + InputLine := IBuf + + AddToBufferHistory(IBuf) + IBuf := '' + BIdx := 1 + IMaxBuffer := MaxBuffer + WriteXY(CounterX1, CounterY1, 7, PadLt(Int2Str(Length(IBuf)), 3, '0')) + WriteXY(CounterX2, CounterY2, 7, PadLt(Int2Str(IMaxBuffer), 3, '0')) + Done := True + + //CIdx := 10 + //TIdx := 10 + CursorBg := '|25' + InputClr := '|16|17|07' + + End + Else + + // Clear input buffer with ESC [sf] + If Ch = Chr(27) Then + Begin + IBuf := '' + BIdx := 1 + RBuf := True + End + Else + Begin + // Handle backspace [sf] + If Ch = Chr(8) Then + Begin + Delete (IBuf, Length(IBuf), 1) + RBuf := True + End + Else + + // Allow characters between #32 and #126 only [sf] + Begin + If Ord(Ch) > 31 and Ord(Ch) < 127 Then + Begin + // Limit input buffer length [sf] + If Length(IBuf) < IMaxBuffer Then + Begin + IBuf := IBuf + Ch + RBuf := True + End + End + + // Ignore any other character [sf] + Else + Ch := '' + End + End + + if Upper(Copy(IBuf,1,3)) = '/R ' and LastPrivMsg <> '' Then + IBuf := '/t ' + LastPrivMsg + ' ' + + // Refresh buffer only if changed [sf] + If RBuf Then + Begin + // Scroll input buffer [sf] + If Length(IBuf) > 78-PromptX-UL Then + DBuf := Copy(IBuf, Length(IBuf) - (77-PromptX-UL), 78-PromptX-UL) + Else + DBuf := IBuf + + // Update input bar [sf] + GoToXy(PromptX,PromptY) + + Write('|16' + MyNamePrompt + InputClr + GetPipe(CIdx) + + DBuf + CursorBg + Cursor + InputClr + '|$X78' + InputBg + '|16') + + // Handle counter color coding [sf] + CClr := 7 + If Length(IBuf) > IMaxBuffer -20 Then + CClr := 14 + If Length(IBuf) > IMaxBuffer -10 Then + CClr := 12 + + // Update characters counter [sf] + WriteXY(CounterX1, CounterY1, CClr, PadLt(Int2Str(Length(IBuf)), 3, '0')) + WriteXY(CounterX2, CounterY2, CClr, PadLt(Int2Str(IMaxBuffer), 3, '0')) + End + GoToXy(PromptX,PromptY) + Until Done +End + +// Clear screen +Procedure DoCls +Var X : Integer = 0 +Begin + For X:=1 To CLBuffer Do + ChatLines[X]:='' + RedrawScreen +End + +Procedure DoScrollBack +Begin + //MenuCmd('GV','mrcscrl;x;y;'+ChatLog+';END') + MenuCmd('GV','mrcscrl;'+ChatLog+' /end') + RedrawScreen +End + +Procedure ShowWelcome +Begin + // Welcome info text [sf] + Add2Chat('* |10Welcome to ' + MRCVersion) + Add2Chat('* |15ESC|10 to clear input buffer, |15UP|10/|15DN|10 arrows for buffer history') + Add2Chat('* |15PGUP|10/|15PGDN|10 to change your chat text color and |15TAB|10 for nick completion') + Add2Chat('* |10The bottom-right heartbeat indicates your status with BBS and server') + Add2Chat('* |10Your maximum message length is |15' + Int2Str(IMaxBuffer)+ '|10 characters') + ShowChat(0) +End + +Procedure ShowChanges +Begin + // Changes info text [sf] + Add2Chat('* |15List of changes from MRC v1.1') + Add2Chat('* |10Completely redesigned Input routine [sf]') + Add2Chat('* |10Ability to receive chat while typing (non-blocking) [sf]') + Add2Chat('* |10Built-in input buffer history [sf]') + Add2Chat('* |10Chat text color changing using PgUp and PgDn [sf]') + Add2Chat('* |10Visual indicator when your nick is mentioned [sf]') + Add2Chat('* |10Input buffer with color coded characters counter [sf]') + Add2Chat('* |10Server latency and synchronization heartbeat indicator [sf]') + Add2Chat('* |10Enlarged view port, more lines are available for the chat [sf]') + Add2Chat('* |10Customizable information scroller [sf]') + Add2Chat('* |10Improvement of performance and responsiveness of the interface [sf]') + Add2Chat('* |10Brand new backend for improved speed and scalability [sf]') + Add2Chat('* |10Nick auto-completion using TAB [sf]') + Add2Chat('* |10Reply to last private message using /r [sf]') + Add2Chat('* |10Add |12t|04rOUt |12s|04lAp|10 using |12/trout|10 [mL]') + Add2Chat('* |10Add |14r|06Ot|131|053 |10encoding using |12/rot13|10 [mL]') + //Add2Chat('* |10') + ShowChat(0) +End + + +Procedure EnterChat +Begin + ShowWelcome // Show welcome text [sf] + + Add2Chat(UpdateStrings(Plyr.EnterChatMe,Plyr.Name,'',MyRoom,MyRoom)) + SendToAllNotMe(UpdateStrings(Plyr.EnterChatRoom,Plyr.Name,'',MyRoom,MyRoom)) + + Delay(20) + SendToServer('IAMHERE') + Delay(20) + SendToServer('BANNERS') + ShowChat(0) +End + +Procedure LeaveChat +Var Str1 : String = '' +Begin + Add2Chat(UpdateStrings(Plyr.LeaveChatMe,Plyr.Name,'',MyRoom,MyRoom)) + SendToAllNotMe(UpdateStrings(Plyr.LeaveChatRoom,Plyr.Name,'',MyRoom,MyRoom)) + ShowChat(0) + Delay(500) + SendToServer('LOGOFF'); + ShowChat(0) +End + +Procedure DoSetList +Var R,S,T : String = '' +Var B : Boolean = False +Begin + S:='False' + If Plyr.UseClock Then + S:='True' + + T:='False' + If Plyr.ShowSmilies Then + T:='True' + + R:='12Hour (HH:MMa or HHMMp)' + If Not Plyr.ClockFormat Then + R:='24Hour (HH:MM)' + + B:=Plyr.UseClock + Plyr.UseClock:=False + Add2Chat('|11List of current |15/SET |11values from your account') + Add2Chat('|15ENTERCHATME |08:|07 '+Plyr.EnterChatMe) + Add2Chat('|15ENTERCHATROOM |08:|07 '+Plyr.EnterChatRoom) + Add2Chat('|15ENTERROOMME |08:|07 '+Plyr.EnterRoomMe) + Add2Chat('|15ENTERROOMROOM |08:|07 '+Plyr.EnterRoomRoom) + Add2Chat('|15LEAVECHATME |08:|07 '+Plyr.LeaveChatMe) + Add2Chat('|15LEAVECHATROOM |08:|07 '+Plyr.LeaveChatRoom) + Add2Chat('|15LEAVEROOMME |08:|07 '+Plyr.LeaveRoomMe) + Add2Chat('|15LEAVEROOMROOM |08:|07 '+Plyr.LeaveRoomRoom) + Add2Chat('|15DEFAULTROOM |08:|07 '+Plyr.DefaultRoom) + Add2Chat('|15NICKCOLOR |08:|07 '+Plyr.NameColor+Plyr.Name) + Add2Chat('|15LTBRACKET |08:|07 '+Plyr.LtBracket) + Add2Chat('|15RTBRACKET |08:|07 '+Plyr.RtBracket) + Add2Chat('|15USECLOCK |08:|07 '+S) + Add2Chat('|15CLOCKFORMAT |08:|07 '+R) + Add2Chat('|15SHOWSMILIES |08:|07 '+T+ ' |08(under construction)|07') + ShowChat(0) + Plyr.UseClock:=B +End + +Procedure DoSetHelp +Var B : Boolean = False +Begin + B:=Plyr.UseClock + Plyr.UseClock:=False + Add2Chat('|15/SET |08<|03tag|08> <|03text|08>') + Add2Chat('|11Use |15SET |11to set various fields to your account') + Add2Chat('|15HELP |03This helps message') + Add2Chat('|15LIST |03List all fields and tabs') + Add2Chat('|15ENTERCHATME |03Displayed to |11me |03when I enter chat.') + Add2Chat('|15ENTERCHATROOM |03Displayed to |11room |03when I enter chat.') + Add2Chat('|15ENTERROOMME |03Displayed to |11me |03when I enter room.' ) + Add2Chat('|15ENTERROOMROOM |03Displayed to |11room |03when I enter room.' ) + Add2Chat('|15LEAVECHATME |03Displayed to |11me |03when I leave chat.' ) + Add2Chat('|15LEAVECHATROOM |03Displayed to |11room |03when I leave chat.' ) + Add2Chat('|15LEAVEROOMME |03Displayed to |11me |03when I leave room.') + Add2Chat('|15LEAVEROOMROOM |03Displayed to |11room |03when I leave room.') + Add2Chat('|15DEFAULTROOM |03Join this room when you join chat.') + Add2Chat('|15NICKCOLOR |03Change my nickname color |11(MCI Pipe codes).' ) + Add2Chat('|15LTBRACKET |03Change my left bracket / color |11(MCI Pipe codes).' ) + Add2Chat('|15RTBRACKET |03Change my right bracket / color |11(MCI Pipe codes).' ) + Add2Chat('|15USECLOCK |03(|15Y|03/|15N|03) Use timestamp in chat') + Add2Chat('|15CLOCKFORMAT |1112 |03or |1124 |03hour clock format') + Add2Chat('|15SHOWSMILIES |03Convert smilies into ANSI smilies (under construction)') + ShowChat(0) + Plyr.UseClock:=B +End + +Procedure ChangeClock(T:Integer;S:String) +Begin + S:=StripB(Upper(S),' ') + Case T Of + 1: Begin + If Pos('YE',S) > 0 Or Pos('TR',S) > 0 Then Begin + Plyr.UseClock:=True + Add2Chat('|11CLOCKFORMAT |08: |15True') + End Else Begin + If Pos('NO',S) > 0 Or Pos('FA',S) > 0 Then Begin + Plyr.UseClock:=False + Add2Chat('|11CLOCKFORMAT |08: |15False') + End Else + Add2Chat('|11Usage: |15/SET USECLOCK YES||TRUE|08 or |15/SET USECLOCK NO||FALSE') + End + ShowChat(0) + End + 2: Begin + If S = '12' Then Begin + Plyr.ClockFormat:=True + Add2Chat('|07CLOCKFORMAT |08: |0712 hour') + End Else Begin + If S = '24' Then Begin + Plyr.ClockFormat:=False + Add2Chat('|07CLOCKFORMAT |08: |0724 hour') + End Else + Add2Chat('|11Usage: |08"|03/SET CLOCKFORMAT 12|08" or "|03/SET CLOCKFORMAT 24|08"') + End + ShowChat(0) + End + End + SavePlyr(Plyr.RecIdx) +End + +Procedure ChangeSmilies(T:Integer;S:String) +Begin + S:=StripB(Upper(S),' ') + Case T Of + 1: Begin + If Pos('YE',S) > 0 Or Pos('TR',S) > 0 Then Begin + Plyr.ShowSmilies:=True + Add2Chat('|11SHOWSMILIES |08: |15True') + End Else Begin + If Pos('NO',S) > 0 Or Pos('FA',S) > 0 Then Begin + Plyr.ShowSmilies:=False + Add2Chat('|11SHOWSMILIES |08: |15False') + End Else + Add2Chat('|11Usage: |15/SET SHOWSMILIES YES||TRUE|08 or |15/SET SHOWSMILIES NO||FALSE') + End + ShowChat(0) + End + End + SavePlyr(Plyr.RecIdx) +End + +Procedure DoSet(Line:String) +Var Tag,Txt : String = '' +Var P : Integer = 0 +Begin + Tag:=WordGet(1,Line,' ') + P:=Length(Tag)+1 + Delete(Line,1,P) + StripB(line,' ') + + Case Upper(Tag) Of + 'HELP': DoSetHelp + 'LIST': DoSetList + 'ENTERCHATME' : Plyr.EnterChatMe:=Line + 'ENTERCHATROOM' : Plyr.EnterChatRoom:=Line + 'ENTERROOMME' : Plyr.EnterRoomMe:=Line + 'ENTERROOMROOM' : Plyr.EnterRoomRoom:=Line + 'LEAVECHATME' : Plyr.LeaveChatMe:=Line + 'LEAVECHATROOM' : Plyr.LeaveChatRoom:=Line + 'LEAVEROOMME' : Plyr.LeaveRoomMe:=Line + 'LEAVEROOMROOM' : Plyr.LeaveRoomRoom:=Line + 'DEFAULTROOM' : Plyr.DefaultRoom:=Line + 'NICKCOLOR' : ChangeNick('C',Line,False) + 'LTBRACKET' : ChangeNick('L',Line,False) + 'RTBRACKET' : ChangeNick('R',Line,False) + 'USECLOCK' : ChangeClock(1,Line) + 'CLOCKFORMAT' : ChangeClock(2,Line) + 'SHOWSMILIES' : ChangeSmilies(1,Line) + '' : DoSetHelp + End + SavePlyr(Plyr.RecIdx) +End + +Procedure DLChatLog +Var X,Y,TS,DS,TempChat : String = '' +Var fptr : File +Begin + DS:=Replace(DateStr(DateTime,1),'/','') + TS:=Replace(TimeStr(DateTime,False),':','') + TempChat:=CfgTempPath+'mrc_chat_'+Replace(SiteTag,' ','_')+'_'+DS+'_'+TS+'.log' + Write('|16|11|CL') + If InputYN('Strip MCI color codes? ') Then Begin + fAssign(fptr,ChatLog,66) + fReset(Fptr) + While Not fEof(Fptr) Do Begin + fReadLn(Fptr,X) + Y:=StripMCI(X) + AppendText(TempChat,Y) + End + fClose(Fptr) + End Else + FileCopy(ChatLog,TempChat) + MenuCmd('F3',TempChat); + FileErase(TempChat) + RedrawScreen; +End + +Procedure Main +Var Done : Boolean = False +Var RestOfLine : String = '' +Var W1,W2,UIL : String = '' +Var IL : String = '' +Begin + Loop:=1 + UpdateScreen + Repeat + Delay(10) + IL:=InputLine + + // Support slash commands even if prefixed with a PIPE code + If Pos('/',StripMCI(IL)) = 1 Then Begin + W1:=Upper(WordGet(1,StripMCI(IL),' ')) + W2:=WordGet(2,IL,' ') + RestOfLine:=IL + Delete(RestOfLine,1,Length(W1)) + RestOfLine:=StripB(RestOfLine,' ') + + Case W1 Of + '/CHANGES' : ShowChanges // Display changes [sf] + '/?' : DoHelp + '/B' : DoBroadcast(IL) + '/BBSES' : SendToServer('CONNECTED') + '/CHANNEL' : SendToServer('CHANNEL') + '/CHATTERS' : SendToServer('CHATTERS') + '/CLS' : DoCls + '/DLCHATLOG' : DLChatLog + '/JOIN' : JoinRoom(W2,True) + '/LIST' : SendToServer('LIST') + '/ME' : DoMeAction(IL) + '/TROUT' : DoTroutAction(IL) + '/Q','/QUIT' : + Begin + LeaveChat + Done := True + End + '/ROOMS' : SendToServer('LIST') + '/SCROLL' : DoScrollBack + '/SET' : DoSet(RestOfLine) + '/TOPIC' : ChangeTopic(RestOfLine) + '/T','/MSG','/M', + '/TELL' : DoPrivateMsg(IL) + '/USERS' : SendToServer('USERS') + '/WHO' : DoWho + '/WHOON' : SendToServer('WHOON') + '/MOTD' : SendToServer('MOTD') + '/HELP' : SendToServer('HELP') + '/INFO' : SendToServer('INFO '+W2) + '/QUOTE' : SendToServer(RestOfLine) + '/VERSION' : + Begin + SendToServer('VERSION') + Add2Chat('|07- |13'+MRCVersion) + End + End + End Else Begin + If Length(IL) > 0 Then + Begin + IL:=Replace(IL, '~', ' ') + SendToRoom(MyNamePrompt+GetPipe(CIdx)+IL) + End + End + Until Done +End + +Begin + CleanOut + GetThisUser + + + // Check if we toggle the NodeMsgFlag + If ACS('OA') Then + Begin + NodeMsgFlag:=True + MenuCmd('GE', '18') + End + + Init + RedrawScreen + EnterChat + JoinRoom(Plyr.DefaultRoom,False) + LoadBanners + NextBanner +// ScrollBanner + Main + Write('|16|11|CL') + + // Reset the NodeMsgFlag if changed + If NodeMsgFlag Then + MenuCmd('GE', '18') + + CleanOut + Halt + +End +