Link to home
Start Free TrialLog in
Avatar of Subhuman
Subhuman

asked on

WinSock, DataArrival and AnsiToRtf

I'm working on a Mud client (isn't everyone?), and after lots of frigging around I've created some subs/functions to convert an ANSI formatted string to an RTF formatted string. I've made a little test app which just takes ANSI formatted text in a TextBox and when a button is pressed converts it and writes it to a RichTextBox, and it works a treat.

Now I've added it to the mud client, and it works ok for small sections of data but when a large amount is sent in a large piece (I'm not sure if it's sent as one string or if it's several smaller strings and that's what's choking it up), it first displays the text correctly, the displays it again after the correct text with all the RTF formatting showing. This doesn't happen for smaller blocks of text (such as room descriptions, etc). If I turn off page-pausing in my mud character's configuration, then the whole lot coming down the tube at once causes the program to crash with a "Runtime Error 5: Invalid procedure call or argument."

I suspect these problems are the result of how I'm using the Winsock control rather than problems with my AnsiToRtf converter, since if I use an older version of the client (pre-ansi support) to do the same command then copy the ansi text to my test app, it handles it superbly.

Here are the relevant subs/functions (if you need to see more, just holler):

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
    Dim DataArrived As String
    Winsock1.GetData DataArrived, vbString
    If Not paused Then
        OutputText ParseData(DataArrived)
        DoTriggers DataArrived
    Else
        stringBuffer = stringBuffer & DataArrived
    End If
End Sub


Public Function ParseData(ByVal theText As String) As String
    Dim temp As String
    Dim i As Integer
    For i = 1 To Len(theText)
        Select Case Asc(Mid(theText, i, 1))
            Case 0      'null
            Case 1      'echo
                If processCommand Then
                    If theCommand = 251 Then
                        localEcho = False
                        processCommand = False
                        theCommand = -1
                    ElseIf theCommand = 252 Then
                        localEcho = True
                        processCommand = False
                        theCommand = -1
                    End If
                End If
            Case 7      'beep
                Beep
            Case 8      'backspace
            Case 9      'horizontal tab
                temp = temp & Chr(9)
            Case 10     'LF
                temp = temp & vbCrLf
            Case 11     'vertical tab
                temp = temp & vbCrLf & vbCrLf
            Case 12     'form feed
            Case 13     'CR
            Case 251    'will (option code)
                If processCommand Then theCommand = 251
            Case 252    'won't (option code)
                If processCommand Then theCommand = 252
            Case 255    'interpret as command (then one or two chars)
                If processCommand Then      ' if processCommand is already set,
                    temp = temp & Chr(255)  ' then it's an escape sequence for
                    processCommand = False  ' chr(255), so add it to the string.
                Else
                    processCommand = True   ' otherwise, it's an escape seq for
                End If                      ' a command.
            Case Else
                temp = temp & Mid(theText, i, 1)
                processCommand = False
                theCommand = -1
        End Select
    Next i
    ParseData = temp
End Function


Public Sub OutputText(ByVal theText As String)
    theText = "" & theText
    theText = AnsiToRtf(theText)
    OutputBox.SelStart = Len(OutputBox.Text)
    OutputBox.SelRTF = theText
    OutputBox.SelStart = Len(OutputBox.Text)
End Sub


Private Sub DoTriggers(ByVal theText As String)
    Dim i As Integer
    For i = 0 To numTriggers - 1
        If InStr(1, theText, GetTriggerByI(i).trigger, vbTextCompare) > 0 Then
            If GetTriggerByI(i).enabled Then SendData GetTriggerByI(i).action
        End If
    Next i
End Sub


Public Function AnsiToRtf(ByVal str As String) As String
    Dim s, c, a, r, cs As String
    Dim i, j, cc, csc As Integer
    Dim ac As AnsiCodeList
    Dim b As Boolean
    b = False
    cs = "{{\colortbl;\red255\green255\blue255;"
    csc = 2
    For i = 1 To Len(str)   ' do strings start at 0 or 1?
        r = "{\cf1 "
        c = Mid(str, i, 1)
        If c = Chr(27) Then
            r = "{"      ' stop rtf code building up over loop
            j = InStr(i, str, "m", vbTextCompare)  ' get index of end of string
'MsgBox "j=" & j
            a = Mid(str, i, (j - i) + 1)    ' get ansi code from string
            ac = GetCodes(a)
'MsgBox "acCount=" & ac.Count
            cc = GetColour(ac)
'MsgBox "cc=" & cc
            'r =        ' rtf code equivalent to 'a'
            If cc >= 0 And cc <= 15 Then    ' is it a valid colour?
                cs = cs & "\red" & GetColourByI(cc).Red & "\green" & GetColourByI(cc).Green & "\blue" & GetColourByI(cc).Blue & ";"
                r = r & "\cf" & csc & " "
                csc = csc + 1
                If b Then s = s & "}"    ' closes previous rtf code
            End If
   
            s = s & r   ' appends rtf code to new string
            i = j       ' go to next char after the code. the Next i line
                        ' will add 1 to this.
            b = True
       
        ElseIf c = Chr(13) Then
            If b Then
                s = s & "\par"  ' if it's inside {} it needs \par instead of vbcrlf
            End If
        ElseIf c = "\" Then
            c = "{\\}"
            s = s & c
        Else
            s = s & c ' if it's a normal character just append it to the new string
        End If
    Next i
    cs = cs & "}"   ' close colortbl string
    'if b then
    s = s & "}}"  ' if rtf has been opened, close it
    AnsiToRtf = cs & s   ' the new string
End Function



-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

I suspect that the problem is the DataArrival event being triggered faster than the data can be processed, but I have no idea how to test this, and for all I know the Winsock control deals with it automatically. It's just a suspicion of mine.

What do I need to do to stop long and/or rapid DataArrival events and subsequent actions from dropping dead?
Avatar of adg
adg

Just a guess- try adding some error checking in your data arrival routine.  Perhaps you are getting an error and handling it elsewhere with a resume.  In particular, if you get an error 126, ignore it and do not process the data.
Avatar of Subhuman

ASKER

What's error 126?

And how would I check for this particular error? When I've needed error checking I've generally just given it an On Error Goto [label], then whacked the label at the bottom of the sub so it drops out (or if it's a function, give it some default values to return then drop out), since the errors that occur generally do so because of empty strings or nonexistant config files, and I always check array/string subscripts before trying to access them.
I should mention that I'm using VB5 here.
I was mixed up on error 126 - that applies to send data not data arrival - sorry!  What you wrote about error checking sounds right to me.  I was just suggesting that you make sure you aren't getting an error in your data arrival routine that might be the cause. Now that I've steered you wrong, I'm going to look at it some more to see if I can spot anything.  
*laughs* okeydoke. :)

Its a little late to be asking this but are you using TCP or UDP?
Disregard previous question - you must be using UDP.  I know you can't tell by my inane comments but I really have done this before. More to come...
Umm, TCP I guess. I don't recall what I specified but since I don't really know anything about UDP I probably went with TCP. Whatever it is, it connects and transfers data alright (excepting this problem).
OK, this is very interesting to me.  I've got my test TCP server and client working.  Can I ask how is numTriggers set and what is the definition of GetTriggerByI?  BTW, I'm using VB6 but hopefully I can replicate the problem.  
Public Type trigger
        trigger As String      ' this is the text that will set the trigger off
        action As String       ' this is send back to server when the trigger is set off
        enabled As Boolean     ' whether the trigger can be set off or not
End Type


Dim triggers() As trigger           ' stores all the users triggers (dynamic array)
Public numTriggers As Integer       ' stores the current size of the array (incremented by AddTrigger function)

Public Function GetTriggerByI(ByVal i As Integer) As trigger
    If i >= LBound(triggers) And i <= UBound(triggers) Then
        GetTriggerByI = triggers(i)
    Else             ' invalid trigger, return default values
        GetTriggerByI.trigger = ""
        GetTriggerByI.action = ""
        GetTriggerByI.enabled = False
    End If
End Function


I also have a GetTriggerByS function which does the same thing, except it takes a String parameter and returns the first (and only; the add method checks for uniqueness) trigger with that string as it's .trigger value.
If you want to see the exact data that is screwing up my program, get a mud client (or plain telnet if you want a headache) and connect to realms.game.org:4000.

Set up a character on there, then once you're through the newbie area (takes about 15 mins) you'll be able to access these commands, which are the ones that screw it up:

config   (displays current configuration options)
score    (your stats. should be able to get this in the newbie zone too)
who <race>  (lists all players of <race>. I use 'who pixie' because that's what I play as)
prac     (shows what skills/spells you have at what level)

There are probably others too, but these are the ones I've noticed because they're ones I use fairly often. They all involve lots of line breaks and lots of colour switching (but if I paste the raw ansi code into my test app it handles all the colour fine), which is what leads me to think it's a problem with how I'm catching and handling data arrivals.
That was a great idea. I modified my FTP test client and connected to realms.game.org:4000.  I started the logon process but wasn't given pixie as a choice, so I'm thinking about selecting vampire instead. Any idea why I don't get pixie as a choice?  Do you think it will make any difference in replicating the problem?  Anyway, I'll take it up again when I get home from work. My next step will be to add the AnsiToRtf formatting logic that you've posted.  Thanks for working with me - I've already learned a lot.  
jeez, I'm doing it again.  I meant TCP client, not FTP client.  And I meant newbie process, not logon process.
Hi again back to this again eh ?

What you really need to do here is to try and make your code as asynchronous as possible.

On method of doing this in VB (I've done this before and it works) is to pass all your data onto a global queue. Something like a collection. You then use a timer or timers to rip data of the tail of the queue.

Just as an example:

Public gQueue as Collection

In your form load or somewhere:
Set gQueue = New Collection

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
   Dim DataArrived As String
   Winsock1.GetData DataArrived, vbString
   If Not paused Then
       gQueue.Add DataArrived
   Else
       stringBuffer = stringBuffer & DataArrived
   End If
End Sub

'Set the timer interval to 1
Private Sub Timer1_Timer()

     Dim strData as String
     strData = gQueue.Item(1)
     gQueue.Remove 1
     OutputText ParseData(strData)
     DoTriggers strData

End Sub

Alternatively Call something like this from Sub Main.
Public Sub ProcessData()

    Do While DoEvents()
       Dim strData as String
       'Anything in the queue?
       if gQueue.Count <> 0 then  
           strData = gQueue.Item(1)
           gQueue.Remove 1
           OutputText ParseData(strData)
           DoTriggers strData
       End If  
    Loop

End Sub


Hope this is of some help.

Vin.
VincentLawlor: I was thinking of doing something like that, but had no idea how. I'll give it a whirl when I get home (currently on campus - tee hee).

> 'Set the timer interval to 1


adg: I doubt it would make any difference in replicating the problem; I have a test mud set up on the computer I dev on, and I get the same problems regardless of race, class, alignment, etc.


If you're going to use the AnsiToRtf method I've got above, perhaps I should send you the module I've got it in, as well as a couple of other modules it's dependant on (Tokenizer, StringFuncs) for functionality that VB5 doesn't provide. I know VB6 has a Split() function, but I don't know whether it works the same as the Tokenizer I've got, and I don't know if my custom Replace() function works the same as the VB6 Replace() function either.

VincentLawlor: That is a very clever suggestion.

Subhuman: Thanks, I would like to see the code.  Do you want to post it here? I really want to recreate the problem and/or see the problem resolved.  
I have my email address in my profile here; send me an email and I'll reply and attach a zip of the project.
Vincent: Added your code. Hasn't made any significant changes that I've picked up on, and it hasn't fixed the problem of doubling up of long text segments, but I imagine it's improved data handling a lot.

adg: Sent you the files.
Doubling up of long text segments as far as I recall happens when using UDP. The host looks like it is using User Datagram Packets to multicast the information.

Just out of interest here's a bit of an Explanation of UDP:

UDP provides a connectionless, unreliable transport service. Connectionless means that a communication session between hosts is not established before exchanging data. UDP is often used for one-to-many communications that use broadcast or multicast IP datagrams. The UDP connectionless datagram delivery service is unreliable because it does not guarantee data packet delivery and no notification is sent if a packet is not delivered. Also, UDP does not guarantee that packets are delivered in the same order in which they were sent.

Because delivery of UDP datagrams is not guaranteed, applications using UDP must supply their own mechanisms for reliability, if needed. Although UDP appears to have some limitations, it is useful in certain situations. For example, Winsock IP multicasting is implemented with UDP datagram type sockets. UDP is very efficient because of low overhead.

Vin.
Hrm, interesting.

How would I work around that? Change the winsock control to use UDP?
You could try changing it to UDP (don't think it will make much difference though).

UDP Peer To Peer Example.

UDP Basics
Creating a UDP application is even simpler than creating a TCP application because the UDP protocol doesn't require an explicit connection. In the TCP application above, one Winsock control must explicitly be set to "listen," while the other must initiate a connection with the Connect method.

In contrast, the UDP protocol doesn't require an explicit connection. To send data between two controls, three steps must be completed (on both sides of the connection):

Set the RemoteHost property to the name of the other computer.


Set the RemotePort property to the LocalPort property of the second control.


Invoke the Bind method specifying the LocalPort to be used. (This method is discussed in greater detail below.)
Because both computers can be considered "equal" in the relationship, it could be called a peer-to-peer application. To demonstrate this, the code below creates a "chat" application that allows two people to "talk" in real time to each other:

To create a UDP Peer

Create a new Standard EXE project.


Change the name of the default form to frmPeerA.


Change the caption of the form to "Peer A."


Draw a Winsock control on the form and name it udpPeerA.


On the Properties page, click Protocol and change the protocol to UDPProtocol.


Add two TextBox controls to the form. Name the first txtSend, and the second txtOutput.


Add the code below to the form.
Private Sub Form_Load()
    ' The control's name is udpPeerA
    With udpPeerA
        ' IMPORTANT: be sure to change the RemoteHost
        ' value to the name of your computer.
        .RemoteHost= "PeerB"
        .RemotePort = 1001   ' Port to connect to.
        .Bind 1002                ' Bind to the local port.
    End With
    frmPeerB.Show                 ' Show the second form.
End Sub

Private Sub txtSend_Change()
    ' Send text as soon as it's typed.
    udpPeerA.SendData txtSend.Text
End Sub

Private Sub udpPeerA_DataArrival _
(ByVal bytesTotal As Long)
    Dim strData As String
    udpPeerA.GetData strData
    txtOutput.Text = strData
End Sub

To create a second UDP Peer

Add a standard form to the project.


Change the name of the form to frmPeerB.


Change the caption of the form to "Peer B."


Draw a Winsock control on the form and name it udpPeerB.


On the Properties page, click Protocol and change the protocol to UDPProtocol.


Add two TextBox controls to the form. Name the TextBox txtSend, and the second txtOutput.


Add the code below to the form.
Private Sub Form_Load()
    ' The control's name is udpPeerB.
    With udpPeerB
        ' IMPORTANT: be sure to change the RemoteHost
        ' value to the name of your computer.
        .RemoteHost= "PeerA"
        .RemotePort = 1002    ' Port to connect to.
        .Bind 1001                ' Bind to the local port.
    End With
End Sub

Private Sub txtSend_Change()
    ' Send text as soon as it's typed.
    udpPeerB.SendData txtSend.Text
End Sub

Private Sub udpPeerB_DataArrival _
(ByVal bytesTotal As Long)
    Dim strData As String
    udpPeerB.GetData strData
    txtOutput.Text = strData
End Sub

To try the example, press F5 to run the project, and type into the txtSend TextBox on either form. The text you type will appear in the txtOutput TextBox on the other form.

Vin.
Your email hasn't arrived - can you send it again?

If you switch to UDP then I think your MUD client won't be compatible with other MUD's because they are using TCP.  But I'm not sure.
Hrm, ok, will do.

*shrugs* guess I'll find out soon enough.
Thanks, I got it.  I just noticed something interesting that might be a clue.  If I run my TCP client and server from the IDE and send 9000 bytes from the server, the client has two data arrivals, the first one is 8192 and the second is 808, as expected.  But if I compile the code and run the client and server from EXE's, when I send 9000 bytes the two data arrivals have length 8192 and 9000. Are you noticing any difference in the problem when running from IDE versus EXE?  
Are you saying that two blocks of data one of 8192 and the other of 9000 are being received or is the total number of bytes 8192 on the first data arrival and 9000 on the second.

Weird...

Vin.
Vin, that is an excellent question.  

In my previous comment I was referring to the value of the bytesTotal field, not the actual length of the received data.  In order to answer Vin's question more precisely, I modified my DataArrival routine as follows and it seems to be much more stable in behavior.  Note the handling of error code zero and the resetting of strData to empty after it has been used.  

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
    On Error GoTo ErrorHandler
    Dim strData As String
    Winsock1.GetData strData, vbString
    Text1.Text = strData
    strData = ""
    Exit Sub
ErrorHandler:
    If Err.Number = 0 Then Resume
    Err.Raise Err.Number
End Sub

I hope that making these changes might make a difference.
BTW, I received a second copy of your email so I guess there is some kind of delay happening.  
Now that I look at it, maybe there should be an "on error goto 0" after the "if err.number = 0 then resume".  Otherwise a non-zero error might cause an unwanted loop.  
I changed my DataArrival method to this, but the problem persists:

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
  On Error Goto DataArrivalError
  Dim DataArrived As String
  Winsock1.GetData DataArrived, vbString
  If Not paused Then
      gQueue.Add DataArrived
  Else
      stringBuffer = stringBuffer & DataArrived
  End If
  DataArrived = ""
  Exit Sub
DataArrivalError:
  Err.Raise Err.Number
End Sub


Also tried playing with UDP, and after screwing around with some settings and commenting out a few bits (like the close connection if Connect event doesn't happen with 10 seconds of opening connection) and could retrieve data from the server - but it balked when it got to Winsock1.SendData with the following error:

Run-time error '10047': Address Family is not supported.

Digging through the documentation was fruitless; it didn't say anything about how to send data while using the UDP protocol, so I couldn't actually send the offending commands to check the results.
Darn, I was hoping that would help.  I've been looking at the program you sent - that's very ambitious work!  I'll try to get something working based on a simplified version and see if that helps.  

In the meanwhile, here is a UDP test peer that works on my machine.  In order to use it, it has to be run twice.  I  compile it and then run two exe instances or run one exe instance and one ide instance. It automatically sets up the ports.  You can see how much simplier UDP is that TCP.  But I think that in any communication both sides must be using the same protocol.  

Option Explicit
Private Sub Form_Load()
    txtSend.Text = ""
    txtReceive.Text = ""
    txtReceive.Locked = True
    On Error GoTo ErrorHandler
    With Winsock1
        .Close
        .RemoteHost = "localhost"
        .Bind 2000
        .RemotePort = 2001
    End With
Exit Sub
ErrorHandler:
    If Err.Number = sckAddressInUse Then
        With Winsock1
            .Close
            .RemoteHost = "localhost"
            .Bind 2001
            .RemotePort = 2000
        End With
    End If
On Error GoTo 0
End Sub
Private Sub Form_Resize()
    cmdClear.Left = Me.Width - cmdClear.Width - 200
    cmdSend.Left = cmdClear.Left
    txtSend.Width = Me.Width - cmdClear.Width - 500
    txtReceive.Width = txtSend.Width
End Sub

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
    'MsgBox "DataArrival Len = " + CStr(bytesTotal)
    Dim strData As String
    Winsock1.GetData strData
    If txtReceive.Text = "" Then
        txtReceive.Text = strData + " (" + CStr(Now) + ")"
    Else
        txtReceive.Text = txtReceive + vbCrLf + strData + " (" + CStr(Now) + ")"
    End If
End Sub
Private Sub cmdSend_Click()
    On Error GoTo ErrorHandler
    Winsock1.SendData txtSend.Text
    txtSend.Text = ""
    Exit Sub
ErrorHandler:
    If Err.Number = 126 Then Resume Next
    MsgBox CStr(Err.Number) + Err.Description + Err.Source
End Sub
Private Sub cmdClear_Click()
    txtReceive.Text = ""
End Sub
Private Sub Winsock1_Error(ByVal Number As Integer, Description As String, ByVal Scode As Long, ByVal Source As String, ByVal HelpFile As String, ByVal HelpContext As Long, CancelDisplay As Boolean)
    MsgBox "Winsock Error: " + CStr(Number) + " " + Description
End Sub
So EE is back up again.

Typed 'config' in two version of the client; one an older one with no ansi parsing, the other the newer version.

This is the data sent by the server including ansi codes and everything.
http://www.djh.f2s.com/dumpage/ansicode.gif

This is how it is appearing:
http://www.djh.f2s.com/dumpage/rtf.gif
Notice it displays twice, fine the first time, then spits out the (incorrect) rtf code the second time.

This is the current metamorphosis of the AnsiToRtf function:

-=-=-=-=-=-=-=-=-=-=-=-=-=-
Public Function AnsiToRtf(ByVal str As String) As String
    Dim s, c, a, r, cs As String
    Dim i, j, cc, csc As Integer
    Dim ac As AnsiCodeList
    Dim b, cr As Boolean
    b = False
    cr = False
    If midc Then
        j = InStr(1, str, "m", vbTextCompare)
        MsgBox "midc j=" & j
        str = Right(str, Len(str) - j)
    End If
    midc = False
    cs = "{{\colortbl;\red255\green255\blue255;"
    csc = 2
    CurrColour = 1
    For i = 1 To Len(str)   ' do strings start at 0 or 1?
        r = "{\cf" & CurrColour & " "
        c = Mid(str, i, 1)
        If c = Chr(27) Then
            midc = False
            r = "{"      ' stop rtf code building up over loop
            j = InStr(i, str, "m", vbTextCompare)  ' get index of end of string
'MsgBox "j=" & j & " i=" & i
            If j = 0 Then
                j = Len(str)
                midc = True
            End If
            a = Mid(str, i, (j - i) + 1)    ' get ansi code from string
            ac = GetCodes(a)
'MsgBox "acCount=" & ac.Count
            cc = GetColour(ac)
'MsgBox "cc=" & cc
            'r =        ' rtf code equivalent to 'a'
            If cc >= 0 And cc <= 15 Then    ' is it a valid colour?
                cs = cs & "\red" & GetColourByI(cc).Red & "\green" & GetColourByI(cc).Green & "\blue" & GetColourByI(cc).Blue & ";"
                r = r & "\cf" & csc & " "
                csc = csc + 1
                If b Then s = s & "}"    ' closes previous rtf code
            End If
   
            s = s & r   ' appends rtf code to new string
            i = j       ' go to next char after the code. the Next i line
                        ' will add 1 to this.
            b = True
            cr = False
        ElseIf c = Chr(13) Then
            'MsgBox "cr"
            If b Then
                s = s & "\par"  ' if it's inside {} it needs \par instead of vbcrlf
            Else
                s = s & vbCrLf
            End If
            cr = True
'        ElseIf c = Chr(10) Then
'            'MsgBox "lf"
'            If b And Not cr Then
'                s = s & "\par"
'            ElseIf Not cr Then
'                s = s & vbCrLf
'            End If
'            cr = False
        ElseIf c = "\" Then
            c = "{\\}"
            s = s & c
            cr = False
        Else
            s = s & c ' if it's a normal character just append it to the new string
            cr = False
        End If
    Next i
    cs = cs & "}"   ' close colortbl string
    'if b then
    s = s & "}}"  ' if rtf has been opened, close it
    AnsiToRtf = cs & s   ' the new string
    CurrColour = cc
End Function

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

midc is a boolean declared outside the function in order to be global (and thus remember it's value between calls) It is used to filter out and ignore ansi codes that have been split up between data arrivals.
Well, the rtf code looks OK to my eyeball but I'm 90% sure that it's not.  Can you make both strings available (ANSI and RTF) so I can use them in my testing? I think maybe a rewrite of AnsiToRTF is in order.  Where did you get it from (or did you write it)?  
Woohoo, I fixed it!

The problem was the default return value in the GetColour function - it was returning a value that was out of range, and the AnsiToRtf was then checking that the value was in range before using the colour. But, if the value was out of range, then as well as not using the colour, it also failed to close the open brace for that colour code.

I modified it so that if a code that didn't contain a colour value showed up, it would keep using the previous colour (and then add/remove 'bright' as appropriate), then removed the if-check in AnsiToRtf so the colour is always added.

This thread has kinda wandered away from the original question, hasn't it? Hehe. :)

Here's the new code for AnsiToRtf and GetColour:

-=-=-=-=-=-=-=-=-=-=-=-

Public Function AnsiToRtf(ByVal str As String) As String
    Dim s, c, a, r, cs As String
    Dim i, j, cc, csc As Integer
    Dim ac As AnsiCodeList
    Dim b, cr As Boolean
    b = False
    cr = False
    If midc Then
        j = InStr(1, str, "m", vbTextCompare)
        MsgBox "midc j=" & j
        str = Right(str, Len(str) - j)
    End If
    midc = False
    cs = "{{\colortbl;\red" & GetColourByI(15).Red & "\green" & GetColourByI(15).Green & "\blue" & GetColourByI(15).Blue & ";"
    csc = 2
    CurrColour = 1
    For i = 1 To Len(str)
'        r = "{\cf" & CurrColour & " "
        c = Mid(str, i, 1)
        If c = Chr(27) Then
            midc = False
            r = "{"      ' stop rtf code building up over loop
            j = InStr(i, str, "m", vbTextCompare)  ' get index of end of string
'MsgBox "j=" & j & " i=" & i
            If j = 0 Then
                j = Len(str)
                midc = True
'            Else
            End If
                a = Mid(str, i, (j - i) + 1)    ' get ansi code from string
                ac = GetCodes(a)
                'MsgBox "acCount=" & ac.Count
                cc = GetColour(ac)
                'MsgBox "cc=" & cc
'            End If
           
            'r =        ' rtf code equivalent to 'a'
'            If cc >= 0 And cc <= 15 Then    ' is it a valid colour?
                gcc = cc Mod 8 ' raw colour value without bright attribute
                cs = cs & "\red" & GetColourByI(cc).Red & "\green" & GetColourByI(cc).Green & "\blue" & GetColourByI(cc).Blue & ";"
                r = r & "\cf" & csc & " "
                csc = csc + 1
                If b Then s = s & "}"    ' closes previous rtf code
'            End If
               
            s = s & r   ' appends rtf code to new string
           
            i = j       ' go to next char after the code. the Next i line
                            ' will add 1 to this.
           
            b = True
            cr = False
        ElseIf c = Chr(13) Then
            'MsgBox "cr"
            If b Then
                s = s & "\par"  ' if it's inside {} it needs \par instead of vbcrlf
            Else
                s = s & vbCrLf
            End If
            cr = True
'        ElseIf c = Chr(10) Then
'            'MsgBox "lf"
'            If b And Not cr Then
'                s = s & "\par"
'            ElseIf Not cr Then
'                s = s & vbCrLf
'            End If
'            cr = False
        ElseIf c = "\" Then
            c = "{\\}"
            s = s & c
            cr = False
        Else
            s = s & c ' if it's a normal character just append it to the new string
            cr = False
        End If
    Next i
    cs = cs & "}"   ' close colortbl string
    'if b then
    s = s & "}}"  ' if rtf has been opened, close it
    AnsiToRtf = cs & s   ' the new string
    CurrColour = cc
End Function

Private Function GetColour(ac As AnsiCodeList) As Integer
    Dim i As Integer
    GetColour = -1
    If ac.Count <= 0 Then Exit Function
    For i = 0 To ac.Count - 1
        If ac.AnsiCodes(i) >= 30 And ac.AnsiCodes(i) <= 37 Then
            GetColour = ac.AnsiCodes(i)
            Exit For
        End If
    Next i
    If GetColour = -1 Then GetColour = gcc
    If HasValue(ac, 0) Then IsBright = False
    If HasValue(ac, 1) Or IsBright Then
        GetColour = GetColour + 8
        IsBright = True
    End If
    GetColour = GetColour - 30
    If GetColour < 0 Or GetColour > 15 Then GetColour = 15
'    MsgBox "Colours = " & GetColour
End Function

-=-=-=-=-=-=-=-=-=-=-=-=-

If you want me to email the whole new project, just say so.

I'm still getting some weirdness when ansi codes are split across two DataArrivals. The second part of each split code is still being output (at the start of the next DataArrival), even though I've included this code to theoretically remove it:

...
    If midc Then
        j = InStr(1, str, "m", vbTextCompare)
        MsgBox "midc j=" & j
        str = Right(str, Len(str) - j)
    End If
...

I'd like to either a) stop the bit of code displaying, or b) re-connect split ansi codes and parse them as normal. Any suggestions?
Congratulations!  Thats great news.  I don't think the question has wandered too far.  In your original question, you mentioned that you thought it was the winsock and not the AnsiToRtf.  It seems at this point that it was the AnsiToRtf but we're still on the same topic.  

Off the top of my head, I like option b.  Can you do something like a deque structure where text can be pushed and popped off either end?  As new text arrives you push it onto the bottom of the deque and when you are ready to process text you pop it off the top of the deque.  So far, this is similar to what you've already done based on a previous suggestion.  The new part would be that ansitortf would recognize when it has an incomplete remainder of text and instead of translating it, would push it back onto the top of the deque to await further processing.  I'm not sure if that is possible given the current design of the ansitortf code - just an idea.
Once again I go ask a question then go off and start coding without waiting to see what comes of it. ;)

I've changed the ansitortf function a tad, so that when it finds an unterminated ansi code, rather than dropping it, it stores it in an external variable 'mids' (which is then accessible next time the ansitortf function is called). Then when the ansitortf runs next time it checks the boolean 'midc' to determine if a code was chopped in half, and if so it prepends the 'mids' variable to the string parameter before it starts parsing, and resets mids and midc.

It seems to work ok, except that in one case (on my local mud) there is a line that is sent where the code is broken in half, and there is a line break before the start of the code. The line break doesn't parse for some reason I can't figure out.

New AnsiToRtf code:

-=-=-=-=-=-=-=-=-=-=-

Public Function AnsiToRtf(ByVal str As String) As String
    Dim s, c, a, r, cs As String
    Dim i, j, cc, csc As Integer
    Dim ac As AnsiCodeList
    Dim b, cr As Boolean
    b = False
    cr = False
    If midc Then
        str = vbCrLf & mids & str
'MsgBox str
    Else
        str = "" & str
    End If
    midc = False
    mids = ""
    cs = "{{\colortbl;\red" & GetColourByI(15).Red & "\green" & GetColourByI(15).Green & "\blue" & GetColourByI(15).Blue & ";"
    csc = 2
    CurrColour = 1
    For i = 1 To Len(str)
        c = Mid(str, i, 1)
        If c = Chr(27) Then
            midc = False
            r = "{"      ' stop rtf code building up over loop
            j = InStr(i, str, "m", vbTextCompare)  ' get index of end of string
'MsgBox "j=" & j & " i=" & i
            If j = 0 Then
                midc = True
                mids = Mid(str, i, Len(str))
                mids = Tokenize(mids, vbCrLf).Tokens(0)
'MsgBox mids
                j = Len(str)
            End If
            a = Mid(str, i, (j - i) + 1)    ' get ansi code from string
            ac = GetCodes(a)
'MsgBox "acCount=" & ac.Count
            cc = GetColour(ac)
'MsgBox "cc=" & cc
               
            gcc = cc Mod 8
            cs = cs & "\red" & GetColourByI(cc).Red & "\green" & GetColourByI(cc).Green & "\blue" & GetColourByI(cc).Blue & ";"
            r = r & "\cf" & csc & " "
            csc = csc + 1
            If b Then s = s & "}"    ' closes previous rtf code
               
            s = s & r   ' appends rtf code to new string
           
            i = j       ' go to next char after the code. the Next i line
                            ' will add 1 to this.
            b = True
            cr = False
        ElseIf c = Chr(13) Then
'MsgBox "cr"
            If b Then
                s = s & "\par"  ' if it's inside {} it needs \par instead of vbcrlf
            Else
                s = s & vbCrLf
            End If
            cr = True
'        ElseIf c = Chr(10) Then
'            'MsgBox "lf"
'            If b And Not cr Then
'                s = s & "\par"
'            ElseIf Not cr Then
'                s = s & vbCrLf
'            End If
'            cr = False
        ElseIf c = "\" Then
            c = "{\\}"
            s = s & c
            cr = False
        Else
            s = s & c ' if it's a normal character just append it to the new string
            cr = False
        End If
    Next i
    cs = cs & "}"   ' close colortbl string
    'if b then
    s = s & "}}"  ' if rtf has been opened, close it
    AnsiToRtf = cs & s   ' the new string
    CurrColour = cc
End Function

-=-=-=-=-=-=-=-=-=-=-

If we can sort that out, great. If not, it's tolerable so I'll probably just leave it be. But might as well at least give it a whack. :)
Sounds like you're making great progress!
Ok, interesting bit of weirdness has come up here. I sent a copy of the program to a friend who also plays RoD, so he could give it a shot, and he's getting line breaks after prompts but before the command he sends.

This is a screenshot of the output for me (as it's supposed to be):
http://www.djh.f2s.com/dumpage/gumboots.gif

This is a screenshot of how it's coming out for him:
http://www.djh.f2s.com/dumpage/gumboots.jpg

Notice how when he types a command like 'sleep' or 'whois self', the command is echoed on the line following the prompt, rather than the same line as it is for me.

I have no idea what might be causing this. It looks almost as if the mud was sending the line breaks in different places for him than it was for me, although I don't see why it would do this.

Any thoughts?
We both get the same line breaks using GMud, which seems to suggest it's not the mud doing it (although it might just be it was out of synch a little, I'll have to get him to check it again).
http://www.djh.f2s.com/dumpage/gmud.gif
ASKER CERTIFIED SOLUTION
Avatar of adg
adg

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Yeah, the logging sounds like a good idea, I'll have to play with that. I know we were using the same executable, because I'd only just sent it to him and was using the same one I sent. :)
Stupid EE downtime.

Thanks for the help. :)

Didn't figure out the weird line breaks on win2k, might work on that some other time.
I just realized you'd accepted my answer.  Thank you and I'm sorry I wasn't more help. As you mentioned, the ee down time made it difficult.