/*
* 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();
}
}
}