grendel-1.0.0a7/backup/
grendel-1.0.0a7/bin/
grendel-1.0.0a7/boards/
grendel-1.0.0a7/clans/
grendel-1.0.0a7/documentation/todo/
grendel-1.0.0a7/help/
grendel-1.0.0a7/logs/
grendel-1.0.0a7/players/
grendel-1.0.0a7/progs/
grendel-1.0.0a7/races/
grendel-1.0.0a7/src/contrib/
grendel-1.0.0a7/src/modules/speller/
grendel-1.0.0a7/src/modules/status/
grendel-1.0.0a7/src/tests/
grendel-1.0.0a7/src/tests/dunit/
{
	Delphi IMC3 Client - Core code and types
	
	Based on client code by Samson of Alsherok.
	
	$Id: imc3_core.pas,v 1.4 2004/03/14 11:54:22 druid Exp $
}

unit imc3_core;

interface


uses
	Classes,
	dtypes,
	socket,
	imc3_chan,
	imc3_mud,
	imc3_packet,
	imc3_const;


type
	GInterMud = class(TThread)
	private
		this_mud : GMud_I3;
		router : GRouter_I3;
		packet : GPacket_I3;
		socket : GSocket;
		connected : boolean;
		
		debugLevel : integer;
		
		reconattempts : integer;
		_wait : integer;

		outputBuffer : string;
	 	inputBuffer : array[0..MAX_IPS - 1] of char;
 		inputPointer : integer;
		
		procedure handleError(packet : GPacket_I3);
		procedure handleStartupReply(packet : GPacket_I3);
		procedure handleMudList(packet : GPacket_I3);
		procedure handleChanList(packet : GPacket_I3);
		procedure handleChannelMessage(packet : GPacket_I3);
		procedure handleChannelEmote(packet : GPacket_I3);
		procedure handleLocateReply(packet : GPacket_I3);
		procedure handleLocateRequest(packet : GPacket_I3);
		procedure handleTell(packet : GPacket_I3);
		procedure handleBeep(packet : GPacket_I3);
		procedure handleWhoReq(packet : GPacket_I3);
		procedure handleWhoReply(packet : GPacket_I3);
		procedure handleFingerReq(packet : GPacket_I3);
		procedure handleFingerReply(packet : GPacket_I3);
		procedure handlePacket(packet : GPacket_I3);

		procedure startup();
		
		procedure debug(msg : string; level : integer = 1);

		procedure writePacket(msg : string);
		
	published
		procedure connect();
		procedure disconnect();
		
		procedure sendPacket();
		
		procedure writeBuffer(msg : string);
		procedure writeHeader(identifier, originator_mudname, originator_username, target_mudname, target_username : string);
		
		procedure sendError(mud, user, code, msg : string);
		procedure sendChannelListen(user : string; channel : GChannel_I3; lconnect : boolean);
		procedure sendChannelMessage(channel : GChannel_I3; name, msg : string);
		procedure sendChannelEmote(channel : GChannel_I3; name, msg : string);
		procedure sendChannelTarget(channel : GChannel_I3; name, tmud, tuser, msg_o, msg_t, tvis : string);
		procedure sendLocateRequest(originator, user : string);
		procedure sendTell(from_user, to_user : string; mud : GMud_I3; msg : string);
		procedure sendBeep(from_user, to_user : string; mud : GMud_I3);
		procedure sendWhoReq(from_user : string; mud : GMud_I3);
		procedure sendFingerReq(from_user, to_user : string; mud : GMud_I3);
		
		procedure shutdown();
		
		constructor Create(debugLevel : integer = 0);
		destructor Destroy; override;
		
		procedure setDebugLevel(debugLevel : integer = 0);
		
		procedure Execute(); override;
		
		property connectedRouter : GRouter_I3 read router;
		property isConnected : boolean read connected;
	end;


implementation

uses
{$IFDEF WIN32}
	WinSock2,
{$ENDIF}
{$IFDEF LINUX}
	Libc,
{$ENDIF}
	SysUtils,
	FastStrings,
	Channels,
	imc3_util,
	constants,
	console,
	chars,
	player,
	conns,
	mudsystem,
	util;


constructor GInterMud.Create(debugLevel : integer = 0);
begin
	inherited Create(false);
	
	connected := false;
	
	Self.debugLevel := debugLevel;
	
	this_mud := GMud_I3.Create();
	this_mud.readConfig();
	
	socket := GSocket4.Create();
	
	reconattempts := 0;
	_wait := 0;
end;

destructor GInterMud.Destroy();
begin
	socket.Free();
	this_mud.Free();
	
	inherited Destroy();
end;

procedure GInterMud.connect();
begin
	if (isConnected) then
		exit;
		
	_wait := 2;
end;

procedure GInterMud.disconnect();
begin
	if (not isConnected) then
		exit;
	
	debug('Disconnecting from server');
		
	connected := false;
	shutdown();
	socket.disconnect();
end;

// bool I3_write_packet( char *msg )
procedure GInterMud.writePacket(msg : string);
var
	oldsize, size,  x : integer;
	s : array of char;
begin
	size := length(msg);
	oldsize := size;
	
	SetLength(s, size + 8);
	
	s[3] := chr(size mod 256);
	size := size shr 8;
	s[2] := chr(size mod 256);
	size := size shr 8;
	s[1] := chr(size mod 256);
	size := size shr 8;
	s[0] := chr(size mod 256);
	
	StrPCopy(@s[4], msg);

  { Scan for \r used in Diku client packets and change to NULL }
  for x := 4 to oldsize + 4 do
    begin
  	if (s[x] = #13) then
  		begin
  		s[x] := #0;
  		end;
    end;
	
	x := socket.send(s[0], oldsize + 4);
	
	if (x <= 0) then
		raise Exception.Create('Write error on socket');
	
	debug('Sent packet: ' + msg, 2);
	
	outputBuffer := '';
end;

// void I3_send_packet( void )
procedure GInterMud.sendPacket();
begin
	writePacket(outputBuffer);
end;

// void I3_write_buffer( const char *msg )
procedure GInterMud.writeBuffer(msg : string);
begin
	outputBuffer := outputBuffer + msg;
end;

// void I3_write_header( char *identifier, char *originator_mudname, char *originator_username, char *target_mudname, char *target_username ) 
procedure GInterMud.writeHeader(identifier, originator_mudname, originator_username, target_mudname, target_username : string);
begin
	writeBuffer('({"');
	writeBuffer(identifier);
	writeBuffer('",5,');

	if (originator_mudname <> '') then
		begin
		writeBuffer('"');
		writeBuffer(originator_mudname);
		writeBuffer('",');
		end
	else
		begin
		writeBuffer('0,');
		end;
	
	if (originator_username <> '') then
		begin
		writeBuffer('"');
		writeBuffer(originator_username);
		writeBuffer('",');
		end
	else
		begin
		writeBuffer('0,');
		end;

	if (target_mudname <> '') then
		begin
		writeBuffer('"');
		writeBuffer(target_mudname);
		writeBuffer('",');
		end
	else
		begin
		writeBuffer('0,');
		end;

	if (target_username <> '') then
		begin
		writeBuffer('"');
		writeBuffer(target_username);
		writeBuffer('",');
		end
	else
		begin
		writeBuffer('0,');
		end;
end;

// void I3_startup_packet( void )
procedure GInterMud.startup();
begin
(*   char s[SMST];

   if( !I3_is_connected() )
	return;

   I3_output_pointer = 4;
   I3_output_buffer[0] = '\0';

   i3log( "Sending startup_packet to %s", this_mud->routerName ); *)

  writeHeader('startup-req-3', this_mud.name, '', router.name, '');

  writeBuffer(IntToStr(this_mud.password));
  writeBuffer(',');
  writeBuffer(IntToStr(this_mud.mudlist_id));
  writeBuffer(',');
  writeBuffer(IntToStr(this_mud.chanlist_id));
  writeBuffer(',');
  writeBuffer(IntToStr(this_mud.player_port));
  writeBuffer(',0,0,"');

	writeBuffer(this_mud.mudlib);
  writeBuffer('","');
	writeBuffer(this_mud.base_mudlib);
  writeBuffer('","');
  writeBuffer(this_mud.driver);
  writeBuffer('","');
  writeBuffer(this_mud.mud_type);
  writeBuffer('","');
  writeBuffer(this_mud.open_status);
  writeBuffer('","');
  writeBuffer(this_mud.admin_email);
  writeBuffer('",');

  { Begin first mapping set }
  writeBuffer('([');

  if (this_mud.emoteto) then
		writeBuffer('"emoteto":1,');
  if (this_mud.news) then
		writeBuffer('"news":1,');
  if (this_mud.ucache) then
		writeBuffer('"ucache":1,');
  if (this_mud.auth) then
		writeBuffer('"auth":1,');
  if (this_mud.locate) then
		writeBuffer('"locate":1,');
  if (this_mud.finger) then
		writeBuffer('"finger":1,');
  if (this_mud.channel) then
		writeBuffer('"channel":1,');
  if (this_mud.who) then
		writeBuffer('"who":1,');
  if (this_mud.tell) then
		writeBuffer('"tell":1,');
  if (this_mud.beep) then
		writeBuffer('"beep":1,');
  if (this_mud.mail) then
		writeBuffer('"mail":1,');
  if (this_mud.mfile) then
		writeBuffer('"file":1,');
  if (this_mud.http > 0) then
  	writeBuffer('"http":' + IntToStr(this_mud.http) + ',');
  if (this_mud.smtp > 0) then
  	writeBuffer('"smtp":' + IntToStr(this_mud.smtp) + ',');
  if (this_mud.pop3 > 0) then
  	writeBuffer('"pop3":' + IntToStr(this_mud.pop3) + ',');
  if (this_mud.ftp > 0) then
  	writeBuffer('"ftp":' + IntToStr(this_mud.ftp) + ',');
  if (this_mud.nntp > 0) then
  	writeBuffer('"nntp":' + IntToStr(this_mud.nntp) + ',');
  if (this_mud.rcp > 0) then
  	writeBuffer('"rcp":' + IntToStr(this_mud.rcp) + ',');
  if (this_mud.amrcp > 0) then
  	writeBuffer('"amrcp":' + IntToStr(this_mud.amrcp) + ',');

  writeBuffer(']),([');

  { END first set of "mappings", start of second set }
  if (this_mud.web <> '') then
  	writeBuffer('"url":"' + this_mud.web + '",');

  writeBuffer('"time":"' + DateTimeToStr(Now) + '",');
  writeBuffer(']),})' + #13);

	sendPacket();
end;

procedure GInterMud.handleError(packet : GPacket_I3);
var
	code, msg, error : string;
	pl : GPlayer;
begin
	code := GString(packet.fields[6]).value;
	msg := GString(packet.fields[7]).value;
	
	pl := GPlayer(findPlayerWorldEx(nil, packet.target_username));
	
	error := Format('Error: from %s to %s@%s: %s (%s)', [packet.originator_mudname, packet.target_username, packet.target_mudname, msg, code]);
	
 	debug(error);

	if (pl <> nil) then
		pl.sendBuffer(error + #13#10);
end;

procedure GInterMud.handleStartupReply(packet : GPacket_I3);
begin
	debug('Accepted by router', 1);
end;

procedure GInterMud.handleMudList(packet : GPacket_I3);
var
	mud : GMud_I3;
  i, j : integer;
  child, list : TList;
  name : string;
begin
  list := TList(packet.fields[7]);
	debug(IntToStr(list.count div 2) + ' muds in packet', 2);

  i := 0;

  while (i < list.count) do
  	begin
    name := GString(list[i]).value;

    mud := GMud_I3(mudList.get(name));

    if (mud = nil) then
    	begin
      debug('New mud: ' + name, 2);

      mud := GMud_I3.Create();
      mud.name := name;
	    mudList.put(mud.name, mud);
      end
    else
      debug('Updating mud: ' + name, 2);

    if (GString(list[i + 1]).value = '0') then
    	begin
      debug(name + ' is down', 2);
      mud.status := 0;
      end
    else
    	begin
	    child := TList(list[i + 1]);
	    
	    if (child.count < 13) then
	    	debug('Illegal mud: ' + name, 1)
	    else
	    	begin
				mud.status := StrToIntDef(GString(child[0]).value, 0);
				mud.ipaddress := GString(child[1]).value;
				mud.player_port := StrToIntDef(GString(child[2]).value, 0);
				mud.imud_tcp_port := StrToIntDef(GString(child[3]).value, 0);
				mud.imud_udp_port := StrToIntDef(GString(child[4]).value, 0);
				mud.mudlib := GString(child[5]).value;
				mud.base_mudlib := GString(child[6]).value;
				mud.driver := GString(child[7]).value;
				mud.mud_type := GString(child[8]).value;
				mud.open_status := GString(child[9]).value;
				mud.admin_email := GString(child[10]).value;

				child := TList(child[11]);
				j := 0;

				while (j < child.count) do
					begin
					if (GString(child[j]).value = 'tell') then
						mud.tell := (StrToIntDef(GString(child[j + 1]).value, 0) = 1)
					else
					if (GString(child[j]).value = 'beep') then
						mud.beep := (StrToIntDef(GString(child[j + 1]).value, 0) = 1)
					else
					if (GString(child[j]).value = 'emoteto') then
						mud.emoteto := (StrToIntDef(GString(child[j + 1]).value, 0) = 1)
					else
					if (GString(child[j]).value = 'who') then
						mud.who := (StrToIntDef(GString(child[j + 1]).value, 0) = 1)
					else
					if (GString(child[j]).value = 'finger') then
						mud.finger := (StrToIntDef(GString(child[j + 1]).value, 0) = 1)
					else
					if (GString(child[j]).value = 'locate') then
						mud.locate := (StrToIntDef(GString(child[j + 1]).value, 0) = 1)
					else
					if (GString(child[j]).value = 'channel') then
						mud.channel := (StrToIntDef(GString(child[j + 1]).value, 0) = 1)
					else
					if (GString(child[j]).value = 'news') then
						mud.news := (StrToIntDef(GString(child[j + 1]).value, 0) = 1)
					else
					if (GString(child[j]).value = 'mail') then
						mud.mail := (StrToIntDef(GString(child[j + 1]).value, 0) = 1)
					else
					if (GString(child[j]).value = 'file') then
						mud.mfile := (StrToIntDef(GString(child[j + 1]).value, 0) = 1)
					else
					if (GString(child[j]).value = 'auth') then
						mud.auth := (StrToIntDef(GString(child[j + 1]).value, 0) = 1)
					else
					if (GString(child[j]).value = 'ucache') then
						mud.ucache := (StrToIntDef(GString(child[j + 1]).value, 0) = 1)
					else
					if (GString(child[j]).value = 'http') then
						mud.http := StrToIntDef(GString(child[j + 1]).value, 0)
					else
					if (GString(child[j]).value = 'ftp') then
						mud.ftp := StrToIntDef(GString(child[j + 1]).value, 0)
					else
					if (GString(child[j]).value = 'nntp') then
						mud.nntp := StrToIntDef(GString(child[j + 1]).value, 0)
					else
					if (GString(child[j]).value = 'smtp') then
						mud.smtp := StrToIntDef(GString(child[j + 1]).value, 0)
					else
					if (GString(child[j]).value = 'rcp') then
						mud.rcp := StrToIntDef(GString(child[j + 1]).value, 0)
					else
					if (GString(child[j]).value = 'amrcp') then
						mud.amrcp := StrToIntDef(GString(child[j + 1]).value, 0);

					inc(j, 2);
					end;
				end;
      end;

    inc(i, 2);
	  end;
end;

procedure GInterMud.handleChanList(packet : GPacket_I3);
var
	chan : GChannel_I3;
  name : string;
  i : integer;
  child, list : TList;
begin
  list := TList(packet.fields[7]);
  i := 0;

	while (i < list.count) do
  	begin
    name := GString(list[i]).value;

    chan := GChannel_I3(chanList.get(name));

    if (chan = nil) then
    	begin
      chan := GChannel_I3.Create();
      chan.I3_name := name;
      chanList.put(chan.I3_name, chan);

      debug('New channel: ' + name, 2);
      end
    else
    	debug('Updating channel: ' + name, 2);

    child := TList(list[i + 1]);

    chan.host_mud := GString(child[0]).value;
    chan.status := StrToIntDef(GString(child[1]).value, 0);

    inc(i, 2);
    end;
end;

procedure GInterMud.handleChannelMessage(packet : GPacket_I3);
var
	channel_name, visname, message, text : string;
	channel : GChannel_I3;
begin
	channel_name := GString(packet.fields[6]).value;
	visname := GString(packet.fields[7]).value;
	message := GString(packet.fields[8]).value;
	text := Format('[%s] %s@%s: %s$7', [channel_name, visname, packet.originator_mudname, message]);
	
	channel := GChannel_I3(chanList[channel_name]);
	
	if (channel <> nil) then
		channel.updateHistory(text);
	
	to_channel(nil, text, CHANNEL_ALL, AT_ECHO);
end;

procedure GInterMud.handleChannelEmote(packet : GPacket_I3);
var
	channel_name, visname, message, text : string;
	channel : GChannel_I3;
begin
	channel_name := GString(packet.fields[6]).value;
	visname := GString(packet.fields[7]).value;
	message := GString(packet.fields[8]).value;
	
	visname := Format('%s@%s', [visname, packet.originator_mudname]);
	
	message := FastReplace(message, '$N', visname);
	
	text := Format('[%s] %s$7', [channel_name, message]);
	
	channel := GChannel_I3(chanList[channel_name]);
	
	if (channel <> nil) then
		channel.updateHistory(text);
	
	to_channel(nil, text, CHANNEL_ALL, AT_ECHO);
end;

procedure GInterMud.handleLocateRequest(packet : GPacket_I3);
var
	username : string;
	pl : GCharacter;
begin
	username := GString(packet.fields[6]).value;
	
	if (existsPlayer(username)) then
		begin
		pl := findPlayerWorldEx(nil, username);
	
		writeHeader('locate-reply', this_mud.name, '', packet.originator_mudname, packet.originator_username);
		writeBuffer('"');
		writeBuffer(this_mud.name);
		writeBuffer('","');
		writeBuffer(pl.name);
		writeBuffer('",0,"');
		
		if (pl <> nil) then
			writeBuffer('Online')
		else
			writeBuffer('Offline');
		
		writeBuffer('",})'#13);
		
		sendPacket();
		end;
end;

procedure GInterMud.handleLocateReply(packet : GPacket_I3);
var
	mudname, visname, status : string;
	pl : GPlayer;
begin
	pl := GPlayer(findPlayerWorldEx(nil, packet.target_username));
	
	mudname := GString(packet.fields[6]).value;
	visname := GString(packet.fields[7]).value;
	status := GString(packet.fields[9]).value;
	
	if (pl = nil) then
		begin
		if (not existsPlayer(packet.target_username)) then
			sendError(packet.originator_mudname, packet.originator_username, 'unk-user', 'No such player.')
		else
			sendError(packet.originator_mudname, packet.originator_username, 'unk-user', 'That player is offline.');
			
		exit;
		end;
		
	pl.sendBuffer('Player "' + visname + '" was located on "' + mudname + '" (' + status + ').'#13#10);
end;

procedure GInterMud.handleTell(packet : GPacket_I3);
var
	visname, msg : string;
	pl : GPlayer;
begin
	pl := GPlayer(findPlayerWorldEx(nil, packet.target_username));
	
	visname := GString(packet.fields[6]).value;
	msg := GString(packet.fields[7]).value;

	if (pl = nil) then
		begin
		if (not existsPlayer(packet.target_username)) then
			sendError(packet.originator_mudname, packet.originator_username, 'unk-user', 'No such player.')
		else
			sendError(packet.originator_mudname, packet.originator_username, 'unk-user', 'That player is offline.');
			
		exit;
		end;
		
	if ((pl.fields['i3flag'] as GBitVector).isBitSet(I3_TELL)) then
		begin
		sendError(packet.originator_mudname, packet.originator_username, 'unk-user', pl.name + ' is not accepting tells.');
		exit;
		end;

	pl.sendBuffer(Format('%s@%s tells you: %s' + #13#10, [visname, packet.originator_mudname, msg]))
end;

procedure GInterMud.handleBeep(packet : GPacket_I3);
var
	visname : string;
	pl : GPlayer;
begin
	pl := GPlayer(findPlayerWorldEx(nil, packet.target_username));
	
	visname := GString(packet.fields[6]).value;

	if (pl = nil) then
		begin
		if (not existsPlayer(packet.target_username)) then
			sendError(packet.originator_mudname, packet.originator_username, 'unk-user', 'No such player.')
		else
			sendError(packet.originator_mudname, packet.originator_username, 'unk-user', 'That player is offline.');
			
		exit;
		end;
	
	if ((pl.fields['i3flag'] as GBitVector).isBitSet(I3_BEEP)) then
		begin
		sendError(packet.originator_mudname, packet.originator_username, 'unk-user', pl.name + ' is not accepting beeps.');
		exit;
		end;
	
	pl.sendBuffer(Format('%s@%s beeps you.'#7 + #13#10, [visname, packet.originator_mudname]))
end;

procedure GInterMud.handleWhoReq(packet : GPacket_I3);
var
	conn : GPlayerConnection;
	iterator : GIterator;
begin
	writeHeader('who-reply', this_mud.name, '', packet.originator_mudname, packet.originator_username);
	writeBuffer('({');
	
	iterator := connection_list.iterator();
	
	while (iterator.hasNext()) do
		begin
		conn := GPlayerConnection(iterator.next());
		
		if (conn.isPlaying()) and (not conn.ch.IS_INVIS) then
			writeBuffer('({"' + escape(conn.ch.name) + '",0,"",}),');
		end;
	
	iterator.Free();

	writeBuffer('}),})'#13);
	sendPacket();	
end;

procedure GInterMud.handleWhoReply(packet : GPacket_I3);
var
	pl : GPlayer;
	list, child : TList;
	i : integer;
	name, title : string;
begin
	pl := GPlayer(findPlayerWorldEx(nil, packet.target_username));
	
	if (pl = nil) then
		begin
		if (not existsPlayer(packet.target_username)) then
			sendError(packet.originator_mudname, packet.originator_username, 'unk-user', 'No such player.')
		else
			sendError(packet.originator_mudname, packet.originator_username, 'unk-user', 'That player is offline.');
			
		exit;
		end;
	
	list := TList(packet.fields[6]);
	i := 0;

	pl.sendBuffer('Who list from "' + packet.originator_mudname + '":'#13#10#13#10);

	while (i < list.count) do
		begin
		child := TList(list[i]);

		name := GString(child[0]).value;
		title := GString(child[2]).value;

		sendToPlayer(pl, Format('%s %s' + #13#10, [name, title]));

		inc(i);
		end;
end;

procedure GInterMud.handleFingerReq(packet : GPacket_I3);
var
	username : string;
	pl : GPlayer;
begin
	username := GString(packet.fields[6]).value;
	pl := GPlayer(findPlayerWorldEx(nil, username));
	
	if (pl = nil) then
		begin
		if (not existsPlayer(username)) then
			sendError(packet.originator_mudname, packet.originator_username, 'unk-user', 'No such player.')
		else
			sendError(packet.originator_mudname, packet.originator_username, 'unk-user', 'That player is offline.');
			
		exit;
		end;
		
	if (pl.IS_INVIS) then
		begin
		sendError(packet.originator_mudname, packet.originator_username, 'unk-user', 'That player is offline.');
		exit;
		end;

	if ((pl.fields['i3flag'] as GBitVector).isBitSet(I3_DENYFINGER)) or ((pl.fields['i3flag'] as GBitVector).isBitSet(I3_PRIVACY)) then
		begin
		sendError(packet.originator_mudname, packet.originator_username, 'unk-user', pl.name + ' is not accepting fingers.');
		exit;
		end;
		
	pl.sendBuffer(Format('%s@%s just requested your i3 finger information.'#13#10, [packet.originator_mudname, packet.originator_username]));

	writeHeader('finger-reply', this_mud.name, '', packet.originator_mudname, packet.originator_username);
	writeBuffer('"');
	writeBuffer(escape(pl.name));
	writeBuffer('","');
	writeBuffer(escape(pl.name + ' ' + pl.title));
	writeBuffer('","","');	

	// No email info in Grendel (yet)
	writeBuffer('Not supported');

	writeBuffer('","');
	writeBuffer('-1');
	writeBuffer('",');
	writeBuffer('-1');
	writeBuffer(',"');
	writeBuffer('[PRIVATE]');
	writeBuffer('","');

	if (pl.IS_IMMORT) then
		writeBuffer(imm_types[pl.level])
	else
		writeBuffer(pl.rank);

	writeBuffer('","Not supported",})'#13);

	sendPacket();
end;

procedure GInterMud.handleFingerReply(packet : GPacket_I3);
var
	pl : GPlayer;
	visname, title, realname, email, last, level, extra : string;
begin
	visname := GString(packet.fields[6]).value;
	title := GString(packet.fields[7]).value;
	realname := GString(packet.fields[8]).value;
	email := GString(packet.fields[9]).value;
	last := GString(packet.fields[10]).value;
	level := GString(packet.fields[13]).value;
	extra := GString(packet.fields[14]).value;
	
	pl := GPlayer(findPlayerWorldEx(nil, packet.target_username));

	
	if (pl = nil) then
		begin
		if (not existsPlayer(packet.target_username)) then
			sendError(packet.originator_mudname, packet.originator_username, 'unk-user', 'No such player.')
		else
			sendError(packet.originator_mudname, packet.originator_username, 'unk-user', 'That player is offline.');
			
		exit;
		end;
		
	sendToPlayer(pl, Format('I3 finger information for %s@%s:'#13#10#13#10, [visname, packet.originator_mudname]));
	sendToPlayer(pl, 'Title: ' + title + #13#10);
	sendToPlayer(pl, 'Real name: ' + realname + #13#10);
	sendToPlayer(pl, 'Level: ' + level + #13#10);
	sendToPlayer(pl, 'E-mail: ' + email + #13#10);
	sendToPlayer(pl, 'Extra info: ' + extra + #13#10);
	sendToPlayer(pl, 'Last on: ' + last + #13#10);
end;

procedure GInterMud.handlePacket(packet : GPacket_I3);
begin
	if (packet.packet_type = 'startup-reply') then
		handleStartupReply(packet)
	else
	if (packet.packet_type = 'mudlist') then
		handleMudList(packet)
	else
	if (packet.packet_type = 'chanlist-reply') then
  		handleChanList(packet)
	else
	if (packet.packet_type = 'channel-m') then
  		handleChannelMessage(packet)
	else
	if (packet.packet_type = 'channel-e') then
  		handleChannelEmote(packet)
	else
	if (packet.packet_type = 'locate-req') then
  		handleLocateRequest(packet)
	else
	if (packet.packet_type = 'locate-reply') then
  		handleLocateReply(packet)
	else
	if (packet.packet_type = 'tell') then
  		handleTell(packet)
	else
	if (packet.packet_type = 'beep') then
  		handleBeep(packet)
	else
	if (packet.packet_type = 'who-req') then
  		handleWhoReq(packet)
	else
	if (packet.packet_type = 'who-reply') then
  		handleWhoReply(packet)
	else
	if (packet.packet_type = 'finger-req') then
  		handleFingerReq(packet)
	else
	if (packet.packet_type = 'finger-reply') then
  		handleFingerReply(packet)
	else
	if (packet.packet_type = 'error') then
  		handleError(packet)
	else
  		begin
	  	debug('unknown packet "' + packet.packet_type + '"', 0);
		debug(packet.toString(), 0);
	  	end;
end;

procedure GInterMud.shutdown();
begin
	saveMudList();
	saveChanList();
	
	writeHeader('shutdown', this_mud.name, '', router.name, '');
	writeBuffer('0');
	writeBuffer(',})' + #13);  

	sendPacket();
end;

procedure GInterMud.Execute();
var
 	ret : integer;
	size : integer;
	msg : string;
 	buf : array[0..MAX_READ - 1] of char;
begin
	Sleep(2500);
	inputPointer := 0;
	
	if (this_mud.name = 'Your Mud Name Here') then
		begin
		writeConsole('I3: Refusing to connect with default values. Please change ' + I3_CONFIG_FILE);
		Terminate();
		end;

	if (this_mud.preferredRouter = nil) then	
		begin
		writeConsole('I3: Impossible to connect to non-existing router');
		exit;
		end;

	router := this_mud.preferredRouter;
	
	if (this_mud.autoconnect) then
		connect();
	
	while (not Terminated) do
		begin
		if (_wait > 0) then
			dec(_wait);

		Sleep(100);

		try
			if (not connected) and (_wait = 1) then
				begin
				debug('Trying to connect to ' + router.name);
				if (socket.connect(router.ipaddress, router.port)) then
					begin
					writeConsole('I3: Connected to ' + router.ipaddress + ' port ' + IntToStr(router.port));

					connected := true;

					startup();
					end
				else
					begin
					writeConsole('I3: Could not connect to ' + router.ipaddress + ' port ' + IntToStr(router.port));
			        	inc(reconattempts);

					if (reconattempts <= 5) then
						_wait := 10
					else
					if (reconattempts <= 20) then
						_wait := 500
					else
						begin
						_wait := -2;	{ Abandon attempts - probably an ISP failure anyway if this happens :) }
						writeConsole('Abandoning attempts to reconnect to Intermud-3 router. Too many failures.');
	   					end;
					end;
				end
			else
			if (connected) then
				begin
				if (socket.canRead()) then
					begin
					ret := socket.read(buf, MAX_READ);

					if (ret > 0) then
						begin
						if (inputPointer + ret > MAX_IPS) then
							debug('Buffer is growing beyond ' + IntToStr(MAX_IPS), 0);
							
						//debug('Read ' + IntToStr(ret) + ' bytes');

						StrMove(@inputBuffer[inputPointer], @buf[0], ret);
						inc(inputPointer, ret);
						end;
					end;

				if (inputPointer > 0) then
					begin
					StrMove(@size, @inputBuffer[0], 4);

					size := ntohl(size);

					//debug('Need ' + IntToStr(size + 4) + ' bytes, currently ' + IntToStr(inputPointer) + ' in buffer');
				
					{ SetLength(msg, inputPointer);
					StrMove(@msg[1], @inputBuffer[4], inputPointer);
					
					for ret := 1 to inputPointer do
						if (msg[ret] = #0) then
							msg[ret] := ' ';
					
					//debug('Packet received so far: ' + msg); }
						
					if (inputPointer >= size + 4) then
						begin
						SetLength(msg, size);

						for ret := 4 to size + 4 do
							if (inputBuffer[ret] = #10) then
								inputBuffer[ret] := ' ';

						StrMove(@msg[1], @inputBuffer[4], size);

						packet := parsePacket(msg);

						debug(msg, 2);
						debug(packet.toString(), 2);

						debug('Got packet: ' + packet.packet_type, 1); 

						debug('Removing head ' + IntToStr(size + 4) + ' bytes from buffer', 2); 
					
						StrMove(@inputBuffer[0], @inputBuffer[size+4], inputPointer - (size + 4));
						dec(inputPointer, size + 4);
						debug('Pointer at ' + IntToStr(inputPointer), 2);

						handlePacket(packet);
						
						packet.Free();
						end;
					end;
				end;
			except
				on E : Exception do 
					begin
					debug(E.Message);
					connected := socket.isValid();
					
					if (not connected) then
						begin
						debug('Connection lost, waiting for reconnect');
						_wait := 500;
						end;
					end;
			end;
		end;
		
	if (connected) then
		disconnect();
end;

// void I3_send_error( char *mud, char *user, char *code, char *message ) 
procedure GInterMud.sendError(mud, user, code, msg : string);
begin
	writeHeader('error', this_mud.name, '', mud, user);
	writeBuffer('"');
	writeBuffer(code);
	writeBuffer('","');
	writeBuffer(msg);
	writeBuffer('",0,})'#13);
	sendPacket();
end;

// void I3_send_channel_message( I3_CHANNEL *channel, char *name, char *message ) 
procedure GInterMud.sendChannelMessage(channel : GChannel_I3; name, msg : string);
begin
	writeHeader('channel-m', this_mud.name, name, '', '');
	writeBuffer('"');
	writeBuffer(channel.I3_name);
	writeBuffer('","');
	writeBuffer(name);
	writeBuffer('","');
	writeBuffer(escape(msg));
	writeBuffer('",})'#13);
	sendPacket();
end;

// void I3_send_channel_emote( I3_CHANNEL *channel, char *name, char *message ) 
procedure GInterMud.sendChannelEmote(channel : GChannel_I3; name, msg : string);
begin
	if (Pos('$N', msg) = 0) then
		msg := '$N ' + msg;
		
	writeHeader('channel-e', this_mud.name, name, '', '');
	writeBuffer('"');
	writeBuffer(channel.I3_name);
	writeBuffer('","');
	writeBuffer(name);
	writeBuffer('","');
	writeBuffer(escape(msg));
	writeBuffer('",})'#13);
	sendPacket();
end;

// void I3_send_channel_t( I3_CHANNEL *channel, char *name, char *tmud, char *tuser, char *msg_o, char *msg_t, char *tvis )
procedure GInterMud.sendChannelTarget(channel : GChannel_I3; name, tmud, tuser, msg_o, msg_t, tvis : string);
begin
	writeHeader('channel-t', this_mud.name, name, '', '');
	writeBuffer('"');
	writeBuffer(channel.I3_name);
	writeBuffer('","');
	writeBuffer(tmud);
	writeBuffer('","');
	writeBuffer(tuser);
	writeBuffer('","');
	writeBuffer(escape(msg_o));
	writeBuffer('","');
	writeBuffer(escape(msg_t));
	writeBuffer('","');
	writeBuffer(name);
	writeBuffer('","');
	writeBuffer(tvis);
	writeBuffer('",})'#13);
	sendPacket();
end;

// void I3_send_channel_listen( I3_CHANNEL *channel, bool lconnect ) 
procedure GInterMud.sendChannelListen(user : string; channel : GChannel_I3; lconnect : boolean);
begin
	writeHeader('channel-listen', this_mud.name, user, router.name, '');
	writeBuffer('"');
	writeBuffer(channel.I3_name);
	writeBuffer('",');
	
	if (lconnect) then
		writeBuffer('1,})'#13)
	else
		writeBuffer('0,})'#13);
		
	sendPacket();
end;

// void I3_send_locate( CHAR_DATA *ch, char *user )
procedure GInterMud.sendLocateRequest(originator, user : string);
begin
	writeHeader('locate-req', this_mud.name, originator, '', '');
	writeBuffer('"');
	writeBuffer(user);
	writeBuffer('",})'#13);
	sendPacket();
end;

// void I3_send_tell( CHAR_DATA *ch, char *to, I3_MUD *mud, char *message )
procedure GInterMud.sendTell(from_user, to_user : string; mud : GMud_I3; msg : string);
begin
	writeHeader('tell', this_mud.name, from_user, mud.name, escape(to_user));
	writeBuffer('"');
	writeBuffer(from_user);
	writeBuffer('","');
	writeBuffer(escape(msg));
	writeBuffer('",})'#13);
	sendPacket();
end;

// void I3_send_beep( CHAR_DATA *ch, char *to, I3_MUD *mud )
procedure GInterMud.sendBeep(from_user, to_user : string; mud : GMud_I3);
begin
	writeHeader('beep', this_mud.name, from_user, mud.name, to_user);
	writeBuffer('"');
	writeBuffer(from_user);
	writeBuffer('",})'#13);
	sendPacket();
end;

// void I3_send_who( CHAR_DATA *ch, char *mud ) 
procedure GInterMud.sendWhoReq(from_user : string; mud : GMud_I3);
begin
	writeHeader('who-req', this_mud.name, from_user, mud.name, '');
	writeBuffer('})'#13);
	sendPacket();	
end;

// void I3_send_finger( CHAR_DATA *ch, char *user, char *mud ) 
procedure GInterMud.sendFingerReq(from_user, to_user : string; mud : GMud_I3);
begin
	writeHeader('finger-req', this_mud.name, from_user, mud.name, '');
	writeBuffer('"');
	writeBuffer(escape(to_user));
	writeBuffer('",');
	writeBuffer('})'#13);
	sendPacket();	
end;

procedure GInterMud.debug(msg : string; level : integer = 1);
begin
	if (level <= debugLevel) then
		writeConsole('I3: ' + msg);
end;

procedure GInterMud.setDebugLevel(debugLevel : integer = 0);
begin
	Self.debugLevel := debugLevel;
end;

end.