/* * Date: 12/15/2005 * Time: 10:51 PM * * Copyright 2005, Static Boy Productions */ using System; using System.Collections; using System.Drawing; using Jessie.Utils; namespace Jessie.TTY { /// /// jtbvTTYReader is a class that connects to a TTY source (such as a telnet or local *nix session of Nethack) and parses the TTY data into message data. That message data is then passed upwards to the jtbvGameData class, which then uses that information and makes it available to the AI. /// public class jtbvTTYReader { #region Special characters private const char BACKSPACE = (char) 127; // Backspace private const char DELETE = (char) 8; // Delete private const char CARRIAGE_RETURN = (char) 10; // Carriage return private const char NEW_LINE = (char) 13; // Newline private const char ESC = (char) 0x1B; // The ESC character used for the VT100 escape codes #endregion Special characters public ArrayList MessageHistory; public ArrayList CurrentMessages; private string currentMessage; private jtbvMessageColor currentMessageColor; private Point currentMessageXY; private Point cursor; private jtbvTextRect screen; public jtbvTTYReader() { MessageHistory = new ArrayList(); CurrentMessages = new ArrayList(); currentMessage = ""; currentMessageColor = new jtbvMessageColor(); currentMessageXY = new Point(); cursor = new Point(); screen = new jtbvTextRect(80, 25); } public Point Cursor { get { return cursor; } } public jtbvTextRect Screen { get { return screen; } } public void ProcessTTY(string recBuf) { #region VT102 escape code processing for (int cnt = 0; cnt < recBuf.Length; cnt++) { if (((char) recBuf[cnt]) == ESC) // VT102 escape code handling... { int parm = 0; ArrayList parmList = new ArrayList(); cnt++; if (cnt >= recBuf.Length) break; switch ((char) recBuf[cnt]) { case '[': cnt++; if (cnt >= recBuf.Length) break; bool cmdHit = false; for (; (!cmdHit) && (cnt < recBuf.Length); cnt++) // This loop parses VT100 commands for coloring and cursor movement { switch ((char) recBuf[cnt]) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': parm *= 10; // Shift the parameter value by base 10 parm += (recBuf[cnt] - 48); // Add the decimal value to the current parameter break; case ';': // Next parameter parmList.Add(parm); parm = 0; break; case 'H': // Move cursor to position if (parmList.Count == 1) { cursor.X = parm - 1; cursor.Y = ((int) parmList[0]) - 1; AlertCurrentMessage(); } else { cursor.X = 0; cursor.Y = 0; AlertCurrentMessage(); } parmList.Clear(); parm = 0; cmdHit = true; break; case 'A': // Move cursor up if (parm == 0) parm = 1; cursor.Y -= parm; AlertCurrentMessage(); parmList.Clear(); parm = 0; cmdHit = true; break; case 'B': // Move cursor down if (parm == 0) parm = 1; cursor.Y += parm; AlertCurrentMessage(); parmList.Clear(); parm = 0; cmdHit = true; break; case 'C': // Move cursor right if (parm == 0) parm = 1; cursor.X += parm; AlertCurrentMessage(); parmList.Clear(); parm = 0; cmdHit = true; break; case 'D': // Move cursor left if (parm == 0) parm = 1; cursor.X -= parm; AlertCurrentMessage(); parmList.Clear(); parm = 0; cmdHit = true; break; case 'J': // Erase screen if (parm == 2) // complete display... { cursor.X = 0; cursor.Y = 0; AlertCurrentMessage(); parmList.Clear(); parm = 0; // Screen.Clear(); } else { parmList.Clear(); parm = 0; Console.WriteLine("Unknown clear request..."); } cmdHit = true; break; case 'K': // Erase to end of current line string line = Screen.GetLine(Cursor.Y); line = jtbvUtils.FixedWidth(line, Cursor.X); Screen.SetLine(Cursor.Y, line); cmdHit = true; break; case 's': // Saved current cursor position //TODO: Implement cursor save eventually? (Needed? It's part of the ANSI standard, not VT100 or VT102) case 'u': // Recall previously saved cursor position //TODO: Implement cursor save eventually? (Needed? It's part of the ANSI standard, not VT100 or VT102) case 'h': // Coloring case 'm': // dGameLaunch seems to also use "m" as opposed to just the ansi standard "h". Wierd. AlertCurrentMessage(); // Before we change the color, we send a new message so as to break messages up according to color parmList.Add(parm); foreach (int p in parmList) { //Console.WriteLine("Text attribute " + p.ToString()); if (p == 0) currentMessageColor = new jtbvMessageColor(); else if ((p >= 1) && (p <= 8)) currentMessageColor.Attr = (jtbvTextAttrib) p; else if ((p >= 30) && (p <= 38)) currentMessageColor.FGColor = (jtbvTextColor) p - 30; else if ((p >= 40) && (p <= 48)) currentMessageColor.BGColor = (jtbvTextColor) p - 40; else Console.WriteLine("Un-understood ANSI coloring option: " + p.ToString()); } parmList.Clear(); parm = 0; cmdHit = true; break; case '?': // Set various options... not sure how to handle... break; case 'r': // not sure what this does...? case 'l': // not sure what this does...? cmdHit = true; break; case 'd': // Set row??? cursor.X = 0; cursor.Y = parm - 1; AlertCurrentMessage(); parmList.Clear(); parm = 0; cmdHit = true; break; default: Console.WriteLine("Didn't understand telnet code (" + recBuf[cnt].ToString() + ") '" + (char) recBuf[cnt] + "'"); Console.WriteLine("Current cnt = " + cnt.ToString()); jtbvUtils.HexPrint(recBuf); cmdHit = true; // Currently, we're in debug mode, and we can't afford to skip this. We need to process this, so throw a fatal exception that halts the program. If we need more debug information to figure out what happened, do a Console.Writeline above. throw new System.Exception("Didn't understand telnet code (" + recBuf[cnt].ToString() + ") '" + (char) recBuf[cnt] + "'"); break; } if (cmdHit) { parmList.Clear(); parm = 0; cnt--; } } break; case '(': // if (((char) recBuf[cnt+1]) == 'B') // Console.WriteLine("Using the United States G1 character set"); // else // Console.WriteLine("Using the " + (char) recBuf[cnt+1] + " character set"); cnt++; break; case ')': // if (((char) recBuf[cnt+1]) == '0') // Console.WriteLine("Using the G0 special characters and line set"); // else // Console.WriteLine("Using the G" + (char) recBuf[cnt+1] + " special characters and line set"); cnt++; break; default: Console.WriteLine("Unknown parameter '" + (char) recBuf[cnt] + "'"); break; } } #endregion VT102 escape code processing else { if (recBuf[cnt] == 0x08) // Backspace character { cursor.X--; AlertCurrentMessage(); } else if (recBuf[cnt] == 0x0A) // Carriage return { cursor.X = 0; AlertCurrentMessage(); } else if (recBuf[cnt] == 0x0D) // Line feed { cursor.Y++; AlertCurrentMessage(); } else if ((recBuf[cnt] >= 32) && (recBuf[cnt] <= 126)) // If it's a normal ASCII character... { currentMessage += (char) recBuf[cnt]; Screen.SetString(Cursor.X, Cursor.Y, ((char) recBuf[cnt]).ToString()); cursor.X++; } } } if (currentMessage.Length > 0) AlertCurrentMessage(); } /// /// This is called whenever the cursor is shifted to a new position, and therefore we likely received a message that our AI might be interested in /// private void AlertCurrentMessage() { if (currentMessage.Length > 0) // If we have any text stored since the last cursor, then save it as a jtbvReceivedMessage object and alert anyone subscribed. { jtbvReceivedMessage newMsg = new jtbvReceivedMessage(currentMessage, currentMessageXY, currentMessageColor); // Console.WriteLine("Received " + newMsg.ToString()); MessageHistory.Add(newMsg); CurrentMessages.Add(newMsg); currentMessage = ""; } currentMessageXY = new Point(Cursor.X, Cursor.Y); // Reset the current message cursor position for the next received message } public void ClearMessages() { CurrentMessages.Clear(); } } }