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/
{
	Summary:
		Player specific functions
	
	## $Id: player.pas,v 1.31 2004/04/15 17:47:22 druid Exp $
}
unit player;

interface


uses
	Classes,
	md5,
	area,
	dtypes,
	conns,
	socket,
	constants,
	chars;


const
	PLAYER_FIELDS_HASHSIZE = 256;		{ estimated size of hash table for dynamic fields }
	PLAYER_MAX_QUEUESIZE = 16;			{ max size of command queue (per client) }


{$M+}
type
	{ The various states of GPlayerConnection}
	GPlayerConnectionStates = (	CON_STATE_PLAYING, CON_STATE_ACCEPTED, CON_STATE_NAME, CON_STATE_PASSWORD, 
												CON_STATE_NEW_NAME, CON_STATE_NEW_PASSWORD, CON_STATE_NEW_RACE, CON_STATE_NEW_SEX,
												CON_STATE_NEW_STATS, CON_STATE_PRESS_ENTER, CON_STATE_MOTD, CON_STATE_EDITING, 
												CON_STATE_LOGGED_OUT,	CON_STATE_CHECK_PASSWORD );

	GPlayer = class;

	GPlayerConnection = class(GConnection)
	protected
		_ch : GPlayer;
		
		_pagepoint : integer;
		pagebuf : string;
		pagecmd : char;
		fcommand : boolean;
		copyover : boolean;
		copyover_name : string;

		state : GPlayerConnectionStates;

		commandQueue : TStringList;

		procedure OnOpenEvent();
		procedure OnInputEvent();
		procedure OnTickEvent();
		procedure OnOutputEvent();
		procedure OnCloseEvent();
		
		procedure clearCommandQueue();
		procedure addCommandQueue(const line : string);
		procedure emptyCommandQueue();
		function checkAliases(line : string) : boolean;

	public
		destructor Destroy(); override;
		constructor Create(socket : GSocket; from_copyover : boolean = false; const copyover_name : string = '');

		procedure writePager(const txt : string);
		procedure setPagerInput(argument : string);
		procedure outputPager();

		function findDualConnection(const name: string) : GPlayer;
		procedure nanny(argument : string);
		
		function isPlaying() : boolean;
		function isEditing() : boolean;
		
		procedure startEditing();
		procedure stopEditing();
		
		procedure pulse();
		
		function stateAsString() : string;

	published
		property pagepoint : integer read _pagepoint write _pagepoint;

		property ch: GPlayer read _ch write _ch;
	end;
	
	GPlayer = class(GCharacter)   	
	protected
		_keylock: boolean;
		_afk : boolean;
		_fields : GHashTable;
		_title : string;                     { Title of PC }
		
		function getField(name : string) : TObject;
		procedure putField(name : string; obj : TObject);

	public
		edit_buffer : string;
		edit_dest : pointer;

		pagerlen : integer;
		age : longint;                     { Age in hours (irl) }
		cfg_flags, flags : cardinal;    { config flags and misc. flags }
		deaths : integer;
		bankgold : longint;           { Gold in bank }
		xptot, xptogo : longint;       { Experience earned total and needed to level }
		fightxp : longint;
		rank : string;
		clanleader : boolean;         { is clanleader? }
		password : string;
		md5_password : MD5Digest;
		prompt : string;
		remorts : integer;            { remorts done }
		condition : array[COND_DRUNK..COND_MAX-1] of integer;
		area: GArea;
		area_fname : string;
		r_lo, r_hi, m_lo, m_hi, o_lo, o_hi : integer;
		wiz_level : integer;          { level of wizinvis }
		bg_status, bg_points : integer;
		bg_room : pointer;
		war_points, quest_points : integer;
		snooping : GCharacter;
		switching : GCharacter;
		reply : GPlayer;
		trophy : array[1..15] of GTrophy;
		trophysize: integer;
		logon_first : TDateTime;
		logon_now : TDateTime;
		played : TDateTime;
		wimpy : integer;
		aliases : GDLinkedList;
		pracs : integer;
		max_skills, max_spells : integer;
		bamfin, bamfout : string;
		taunt : string;
		channels : GDLinkedList;
		// profession:PROF_DATA;

		ld_timer : integer;

		conn : GPlayerConnection;

		active_board : integer;
		boards : array[BOARD1..BOARD_MAX-1] of integer;
		subject : string;

		constructor Create(conn : GPlayerConnection);
		destructor Destroy(); override;

		function ansiColor(color : integer) : string; override;

		function IS_IMMORT : boolean; override;
		function IS_WIZINVIS : boolean; override;
		function IS_HOLYWALK : boolean; override;
		function IS_HOLYLIGHT : boolean; override;
		function IS_AFK : boolean; override;
		function IS_KEYLOCKED : boolean; override;
		function IS_EDITING : boolean; override;
		function IS_DRUNK : boolean; override;

		function getUsedSkillslots() : integer;       // returns nr. of skillslots occupied
		function getUsedSpellslots() : integer;       // returns nr. of spellslots occupied

		function load(const fn : string) : boolean;
		function save(const fn : string) : boolean;

		function getAge() : integer;
		function getPlayed() : integer;

		procedure die; override;
		procedure calcRank();

		procedure quit;

		procedure sendPrompt; override;
		procedure sendBuffer(const s : string); override;
		procedure sendPager(const txt : string); override;
		procedure emptyBuffer; override;

		procedure startEditing(const text : string);
		procedure stopEditing();
		procedure editBuffer(text : string);
		procedure sendEdit(const text : string);

		property fields[name : string] : TObject read getField write putField; 
		property keylock : boolean read _keylock write _keylock;
		property afk : boolean read _afk write _afk;	

	published
		property title : string read _title write _title;
	end;

	GPlayerField = class
	protected
  	_name : string;
  	
	public		
  		constructor Create(const name : string);
  	
		function default() : TObject; virtual; abstract;
		function fromString(const s : string) : TObject; virtual; abstract;
		function toString(x : TObject) : string; virtual; abstract;
		
		property name : string read _name;
	end;
	
	GPlayerFieldFlag = class(GPlayerField)
	public
	  	function default() : TObject; override;
  		function fromString(const s : string) : TObject; override;
	  	function toString(x : TObject) : string; override;	
	end;

	GPlayerFieldInteger = class(GPlayerField)
	public
  		function default() : TObject; override;
	  	function fromString(const s : string) : TObject; override;
  		function toString(x : TObject) : string; override;	
	end;
	
	GPlayerFieldString = class(GPlayerField)
	public
	  	function default() : TObject; override;
  		function fromString(const s : string) : TObject; override;
	  	function toString(x : TObject) : string; override;	
	end;
{$M-}


var
	fieldList : GHashTable;

procedure registerField(field : GPlayerField);
procedure unregisterField(const name : string);

function findPlayerWorld(ch : GCharacter; name : string) : GCharacter;
function findPlayerWorldEx(ch : GCharacter; name : string) : GCharacter;

function existsPlayer(const name : string) : boolean;
procedure acceptConnection(list_socket : GSocket);

procedure initPlayers();
procedure cleanupPlayers();

function act_string(const acts : string; to_ch, ch : GCharacter; arg1, arg2 : pointer) : string;
function act_color(to_ch : GCharacter; const acts : string; sep : char) : string;

procedure act(atype : integer; const acts : string; hideinvis : boolean; ch : GCharacter; arg1, arg2 : pointer; typ : integer);

function playername(from_ch, to_ch : GCharacter) : string;


implementation


uses
	Math,
	SysUtils,
	FastStrings,
	FastStringFuncs,
	ansiio,
	timers,
	console,
	util,
	debug,
	strip,
	commands,
	skills,
	fsys,
	race,
	mudsystem,
	mudhelp,
	clan,
	events,
	server,
	bulletinboard,
	Channels;
	

{ Array with symbolic names for connection states }
var
	con_states : array[CON_STATE_PLAYING..CON_STATE_CHECK_PASSWORD ] of string = (
							'CON_STATE_PLAYING', 'CON_STATE_ACCEPTED', 'CON_STATE_NAME',
							'CON_STATE_PASSWORD', 'CON_STATE_NEW_NAME', 'CON_STATE_NEW_PASSWORD',
							'CON_STATE_NEW_SEX', 'CON_STATE_NEW_RACE', 'CON_STATE_NEW_STATS',
							'CON_STATE_PRESS_ENTER', 'CON_STATE_MOTD', 'CON_STATE_EDITING',
							'CON_STATE_LOGGED_OUT', 'CON_STATE_CHECK_PASSWORD');


{ GPlayerConnection constructor }
constructor GPlayerConnection.Create(socket : GSocket; from_copyover : boolean = false; const copyover_name : string = '');
begin
	inherited Create(socket);
	
	FOnOpen := OnOpenEvent;
	FOnClose := OnCloseEvent;
	FOnTick := OnTickEvent;
	FOnInput := OnInputEvent;
	FOnOutput := OnOutputEvent;

	state := CON_STATE_NAME;

	ch := GPlayer.Create(Self);

	copyover := from_copyover;
	Self.copyover_name := copyover_name;

	commandQueue := TStringList.Create();
end;

{ GPlayerConnection destructor }
destructor GPlayerConnection.Destroy();
begin
	commandQueue.Clear();
	commandQueue.Free();
	
	inherited Destroy();
end;

{ Fired by timer 4 times per second }
procedure GPlayerConnection.pulse();
begin
	inc(_idle);
	
	if ((state = CON_STATE_NAME) and (idle > IDLE_NAME)) or
		((state <> CON_STATE_PLAYING) and (idle > IDLE_NOT_PLAYING)) or
		((idle > IDLE_PLAYING) and (ch <> nil) and (not ch.afk) and (not ch.IS_IMMORT)) or
		((idle > IDLE_AFK) and (ch.afk)) then
		begin
		send(#13#10'You have been idle too long. Disconnecting.'#13#10);
		Terminate();

		exit;
		end;

	if (state = CON_STATE_PLAYING) and (not ch.in_command) then
		ch.emptyBuffer();

	if (state = CON_STATE_PLAYING) and (ch.wait > 0) then
		dec(ch.wait);
end;

{ Event handler for OnOpen }
procedure GPlayerConnection.OnOpenEvent();
var
	temp_buf : string;
begin
	if (not copyover) then
		begin
		state := CON_STATE_NAME;
		
		send(AnsiColor(2,0) + findHelp('M_DESCRIPTION_').text);

		temp_buf := AnsiColor(6,0) + #13#10;
		temp_buf := temp_buf + version_info + ', ' + version_number + '.'#13#10;
		temp_buf := temp_buf + version_copyright + '.';
		temp_buf := temp_buf + AnsiColor(7,0) + #13#10;

		send(temp_buf);

		send(#13#10#13#10'Enter your name or CREATE to create a new character.'#13#10'Please enter your name: ');
		end
	else
  		begin
		state := CON_STATE_MOTD;
    
		ch.setName(copyover_name);
		ch.load(copyover_name);
		send(#13#10#13#10'Gradually, the clouds form real images again, recreating the world...'#13#10);
		send('Copyover complete!'#13#10);

		nanny('');
		end;
end;

{ Event handler for OnClose }
procedure GPlayerConnection.OnCloseEvent();
begin
	if (state = CON_STATE_LOGGED_OUT) then
		dec(system_info.user_cur)
	else
	if (not ch.CHAR_DIED) and ((state = CON_STATE_PLAYING) or (state = CON_STATE_EDITING)) then
		begin
		writeConsole('(' + IntToStr(socket.getDescriptor) + ') ' + ch.name + ' has lost the link');

		act(AT_REPORT,'$n has lost $s link.', false, ch, nil, nil, TO_ROOM);

		if (ch.level >= LEVEL_IMMORTAL) then
			interpret(ch, 'return');
			
		ch.conn := nil;
		SET_BIT(ch.flags, PLR_LINKLESS);	
		end
	else
		begin
		ch.Free();
		end;	
end;

{ Event handler for OnTick }
procedure GPlayerConnection.OnTickEvent();
begin
	if (fcommand) then
		begin
		if (pagepoint <> 0) then
			outputPager()
		else
			ch.emptyBuffer();
			
		fcommand := false;
		end;
	
	emptyCommandQueue();
end;

{ Event handler for OnInput }
procedure GPlayerConnection.OnInputEvent();
var
	cmdline : string;
	i : integer;
begin
	cmdline := trim(comm_buf);

	i := pos(#13, cmdline);
	if (i <> 0) then
		delete(cmdline, i, 1);

	i := pos(#10, cmdline);
	if (i <> 0) then
		delete(cmdline, i, 1);
		
	comm_buf := '';

	fcommand := true;

	if (pagepoint <> 0) then
		setPagerInput(cmdline)
	else
		case state of
			CON_STATE_PLAYING: 	begin
						if (IS_SET(ch.flags,PLR_FROZEN)) and (cmdline <> 'quit') then
							begin
							ch.sendBuffer('You have been frozen by the gods and cannot do anything.'#13#10);
							ch.sendBuffer('To be unfrozen, send an e-mail to the administration, '+system_info.admin_email+'.'#13#10);
							exit;
							end;
						
						if (not checkAliases(cmdline)) then
							addCommandQueue(cmdline);

						emptyCommandQueue();
						end;
			CON_STATE_EDITING: ch.editBuffer(cmdline);
			else
				nanny(cmdline);
		end;
end;

{ Find aliases matching 'line' }
function GPlayerConnection.checkAliases(line : string) : boolean;
var
	iterator : GIterator;
	al : GAlias;
	cmdline, ale, param : string;
begin
	Result := false;
	
	param := one_argument(line, cmdline);
	cmdline := uppercase(cmdline);

	iterator := ch.aliases.iterator();

	while (iterator.hasNext()) do
		begin
		al := GAlias(iterator.next());
		
		if (uppercase(al.alias) = cmdline) then
			begin
			ale := stringreplace(al.expand, '%', param, [rfReplaceAll]);

			while (pos(':', ale) > 0) do
				begin
				line := left(ale, ':');
				ale := right(ale, ':');

				addCommandQueue(line);
				end;

			addCommandQueue(ale);
			
			Result := true;

			break;
			end;
		end;

	iterator.Free();
end;

{ Event handler for OnOutput }
procedure GPlayerConnection.OnOutputEvent();
begin
	ch.sendPrompt();
end;

{ Flush all lines from the command queue }
procedure GPlayerConnection.clearCommandQueue();
begin
	commandQueue.Clear();
end;

{ Add a line to the command queue}
procedure GPlayerConnection.addCommandQueue(const line : string);
begin
	if (commandQueue.Count < PLAYER_MAX_QUEUESIZE) then
		begin
		commandQueue.Add(line);
		end
	else
		begin
		ch.sendBuffer('Stop spamming all those commands!'#13#10);
		end;
end;

{ Execute all lines in the command queue }
procedure GPlayerConnection.emptyCommandQueue();
begin
	while (commandQueue.Count > 0) and (not Terminated) do
		begin
		if (ch.wait > 0) then
			break;
		
		ch.in_command := true;

		interpret(ch, commandQueue[0]);
		
		commandQueue.delete(0);

		if (not ch.CHAR_DIED) then
			ch.in_command := false;
		end;
end;

{ Returns true if connection has state CON_STATE_PLAYING }
function GPlayerConnection.isPlaying() : boolean;
begin
	Result := (state = CON_STATE_PLAYING);
end;

{ Returns true if connection has state CON_STATE_EDITING }
function GPlayerConnection.isEditing() : boolean;
begin
	Result := (state = CON_STATE_EDITING);
end;

{ Sets the state to CON_STATE_EDITING }
procedure GPlayerConnection.startEditing();
begin
	state := CON_STATE_EDITING;
end;

{ Sets the state to CON_STATE_PLAYING }
procedure GPlayerConnection.stopEditing();
begin
	state := CON_STATE_PLAYING;
end;

{ Returns a text version of the state of the connection }
function GPlayerConnection.stateAsString() : string;
begin
	Result := con_states[state];
end;

{ Find out wether 'name' is already connected }
function GPlayerConnection.findDualConnection(const name: string): GPlayer;
var
	iterator : GIterator;
	dual: GPlayerConnection;
begin
	Result := nil;
	iterator := connection_list.iterator();

	while (iterator.hasNext()) do
  		begin
		dual := GPlayerConnection(iterator.next());

		// is there another conn with exactly the same name?
		if  (dual <> Self)  and (Assigned(dual)) and Assigned(dual.ch) and (lowercase(dual.ch.name) = lowercase(name)) then
			begin
			Result := dual.ch;
			exit;
			end;
		end;
	  
	iterator.Free();
end;

procedure GPlayerConnection.nanny(argument : string);
var 
	vict : GPlayer;
	tmp : GCharacter;
	iterator : GIterator;
	race : GRace;
	digest : MD5Digest;
	top, x, temp : integer;
	buf, pwd : string;
begin
	case state of
        CON_STATE_NAME: begin
                  pwd := one_argument(argument, argument);
                  
                  if (length(argument) = 0) then
                    begin
                    send('Please enter your name: ');
                    exit;
                    end;

                  if (uppercase(argument) = 'CREATE') then
                    begin
                    if (system_info.deny_newplayers) then
                    begin
                      send(#13#10'Currently we do not accept new players. Please come back some other time.'#13#10#13#10);
                      send('Name: '); 
                      exit;
                    end
                    else
                    begin
                      send(#13#10'By what name do you wish to be known? ');
                      state := CON_STATE_NEW_NAME;
                      exit;
                    end;
                    end;
                    
                  if (isNameBanned(argument)) then
                    begin;
                    send('Illegal name.'#13#10);
                    send('Please enter your name: ');
                    exit;
                    end;

                  vict := findDualConnection(argument); // returns nil if player is not yet connected

                  if (vict <> nil) and (not vict.IS_NPC) and (vict.conn <> nil) and (cap(vict.name) = cap(argument)) then
                    begin
                    if (not MD5Match(MD5String(pwd), GPlayer(vict).md5_password)) then
                      begin
                      send(#13#10'You are already logged in under that name! Type your name and password on one line to break in.'#13#10);
                      Terminate();
                      end
                    else
                      begin
                      GConnection(vict.conn).Terminate();

                      while (not IS_SET(vict.flags, PLR_LINKLESS)) do;

                      vict.conn := Self;
                      ch := vict;
                      REMOVE_BIT(ch.flags,PLR_LINKLESS);
                      state := CON_STATE_PLAYING;
                      ch.sendPrompt();
                      end;

                    exit;
                    end;

                  if (not ch.load(argument)) then
                    begin
                    send(#13#10'Are you sure about that name?'#13#10'Name: ');
                    exit;
                    end;

                  state := CON_STATE_PASSWORD;
                  send('Password: ');
                  end;
    CON_STATE_PASSWORD: begin
                  if (length(argument) = 0) then
                    begin
                    send('Password: ');
                    exit;
                    end;

                  if (not MD5Match(MD5String(argument), ch.md5_password)) then
                    begin
                    writeConsole('(' + inttostr(socket.getDescriptor) + ') Failed password');
                    send('Wrong password.'#13#10);
                    send('Password: ');
                    exit;
                    end;

                  vict := findDualConnection( ch.name); // returns nil if player is not dual connected

                  if (not Assigned(vict)) then
                    vict := GPlayer(findPlayerWorldEx(nil, ch.name));

                  if (vict <> nil) and (vict.conn = nil) then
                    begin
                    ch.Free();

                    ch := vict;
                    vict.conn := Self;

                    ch.ld_timer := 0;

                    send('You have reconnected.'#13#10);
                    act(AT_REPORT, '$n has reconnected.', false, ch, nil, nil, TO_ROOM);
                    REMOVE_BIT(ch.flags, PLR_LINKLESS);
                    writeConsole('(' + inttostr(socket.getDescriptor) + ') ' + ch.name + ' has reconnected');

                    ch.sendPrompt();
                    state := CON_STATE_PLAYING;
                    exit;
                    end;

                  if (ch.IS_IMMORT) then
                    send(ch.ansiColor(2) + #13#10 + findHelp('IMOTD').text)
                  else
                    send(ch.ansiColor(2) + #13#10 + findHelp('MOTD').text);

                  send('Press Enter.'#13#10);
                  state := CON_STATE_MOTD;
                  end;
        CON_STATE_MOTD: begin
                  send(ch.ansiColor(6) + #13#10#13#10'Welcome, ' + ch.name + ', to this MUD. May your stay be pleasant.'#13#10);

                  with system_info do
                    begin
                    user_cur := connection_list.size();
                    if (user_cur > user_high) then
                      user_high := user_cur;
                    end;

                  ch.toRoom(ch.room);

                  act(AT_WHITE, '$n enters through a magic portal.', true, ch, nil, nil, TO_ROOM);
                  writeConsole('(' + inttostr(socket.getDescriptor) + ') '+ ch.name +' has logged in');

                  ch.node_world := char_list.insertLast(ch);
                  ch.logon_now := Now;

                  if (ch.level = LEVEL_RULER) then
                    interpret(ch, 'uptime')
                  else
                    ch.sendPrompt();

                  state := CON_STATE_PLAYING;
                  fcommand := true;
                 
                  raiseEvent('char-login', ch);
                  end;
    CON_STATE_NEW_NAME: begin
                  if (length(argument) = 0) then
                    begin
                    send('By what name do you wish to be known? ');
                    exit;
                    end;

                  if (FileExists('players\' + argument + '.usr')) or (findDualConnection(argument) <> nil) then
                    begin
                    send('That name is already used.'#13#10);
                    send('By what name do you wish to be known? ');
                    exit;
                    end;

                  tmp := findPlayerWorldEx(nil, argument);
                  
                  if (isNameBanned(argument)) or (tmp <> nil) then
                    begin
                    send('That name cannot be used.'#13#10);
                    send('By what name do you wish to be known? ');
                    exit;
                    end;

                  if (length(argument) < 3) or (length(argument) > 15) then
                    begin
                    send('Your name must be between 3 and 15 characters long.'#13#10);
                    send('By what name do you wish to be known? ');
                    exit;
                    end;

                  ch.setName(cap(argument));
                  state := CON_STATE_NEW_PASSWORD;
                  send(#13#10'Allright, '+ch.name+', choose a password: ');
                  end;
CON_STATE_NEW_PASSWORD: begin
                  if (length(argument)=0) then
                    begin
                    send('Choose a password: ');
                    exit;
                    end;

                  ch.md5_password := MD5String(argument);
                  state := CON_STATE_CHECK_PASSWORD;
                  send(#13#10'Please retype your password: ');
                  end;
CON_STATE_CHECK_PASSWORD: begin
                    if (length(argument) = 0) then
                      begin
                      send('Please retype your password: ');
                      exit;
                      end;

                    if (not MD5Match(MD5String(argument), ch.md5_password)) then
                      begin
                      send(#13#10'Password did not match!'#13#10'Choose a password: ');
                      state := CON_STATE_NEW_PASSWORD;
                      exit;
                      end
                    else
                      begin
                      state := CON_STATE_NEW_SEX;
                      send(#13#10'What sex do you wish to be (M/F/N): ');
                      exit;
                      end;
                    end;
     CON_STATE_NEW_SEX: begin
                  if (length(argument) = 0) then
                    begin
                    send('Choose a sex (M/F/N): ');
                    exit;
                    end;

                  case upcase(argument[1]) of
                    'M':ch.sex:=0;
                    'F':ch.sex:=1;
                    'N':ch.sex:=2;
                  else
                    begin
                    send('That is not a valid sex.'#13#10);
                    send('Choose a sex (M/F/N): ');
                    exit;
                    end;
                  end;

                  state := CON_STATE_NEW_RACE;
                  send(#13#10'Available races: '#13#10#13#10);

                  iterator := raceList.iterator();

                  while (iterator.hasNext()) do
                    begin
                    race := GRace(iterator.next());
                    
                    if (race.convert) then
                      begin
	                    buf := '  [' + ANSIColor(11,0) + race.short + ANSIColor(7,0) + ']  ' + pad_string(race.name, 15);

	                    if (race.def_alignment < 0) then
	                      buf := buf + ANSIColor(12,0) + '<- EVIL' + ANSIColor(7,0);

	                    buf := buf + #13#10;

	                    send(buf);
	                    end;
                    end;
                    
                  iterator.Free();

                  send(#13#10'Choose a race: ');
                  end;
    CON_STATE_NEW_RACE: begin
                  if (length(argument) = 0) then
                    begin
                    send(#13#10'Choose a race: ');
                    exit;
                    end;

                  race := findRace(argument);

                  if (race = nil) or (race.convert = false) then
                    begin
                    send('Not a valid race.'#13#10);										
                    send(#13#10'Choose a race: ');
                    exit;
                    end;

                  ch.race := race;
                  send(race.description + #13#10#13#10);
                  send('250 stat points will be randomly distributed over your five attributes.'#13#10);
                  send('It is impossible to get a lower or a higher total of stat points.'#13#10);

                  with ch do
                    begin
                    top:=250;

                    str:=URange(25,random(UMax(top,75))+ch.race.str_bonus,75);
                    dec(top,str);

                    con:=URange(25,random(UMax(top,75))+ch.race.con_bonus,75);
                    dec(top,con);

                    dex:=URange(25,random(UMax(top,75))+ch.race.dex_bonus,75);
                    dec(top,dex);

                    int:=URange(25,random(UMax(top,75))+ch.race.int_bonus,75);
                    dec(top,int);

                    wis:=URange(25,random(UMax(top,75))+ch.race.wis_bonus,75);
                    dec(top,wis);

                    while (top>0) do
                      begin
                      x:=random(5);

                      case x of
                        0:begin
                          temp:=UMax(75-str,top);
                          if (temp>0) then
                            begin
                            dec(top,temp);
                            str := str + temp;
                            end;
                          end;
                        1:begin
                          temp:=UMax(75-con,top);
                          if (temp>0) then
                            begin
                            dec(top,temp);
                            con := con + temp;
                            end;
                          end;
                        2:begin
                          temp:=UMax(75-dex,top);
                          if (temp>0) then
                            begin
                            dec(top,temp);
                            dex := dex + temp;
                            end;
                          end;
                        3:begin
                          temp:=UMax(75-int,top);
                          if (temp>0) then
                            begin
                            dec(top,temp);
                            int := int + temp;
                            end;
                          end;
                        4:begin
                          temp:=UMax(75-wis,top);
                          if (temp>0) then
                            begin
                            dec(top,temp);
                            wis := wis + temp;
                            end;
                          end;
                      end;
                      end;

                    //top:=str+con+dex+int+wis;
                    end;

                  send(#13#10'Your character statistics are: '#13#10#13#10);

                  buf := 'Strength:     '+ANSIColor(10,0)+inttostr(ch.str)+ANSIColor(7,0)+#13#10 +
                         'Constitution: '+ANSIColor(10,0)+inttostr(ch.con)+ANSIColor(7,0)+#13#10 +
                         'Dexterity:    '+ANSIColor(10,0)+inttostr(ch.dex)+ANSIColor(7,0)+#13#10 +
                         'Intelligence: '+ANSIColor(10,0)+inttostr(ch.int)+ANSIColor(7,0)+#13#10 +
                         'Wisdom:       '+ANSIColor(10,0)+inttostr(ch.wis)+ANSIColor(7,0)+#13#10;
                  send(buf);

                  send(#13#10'Do you wish to (C)ontinue, (R)eroll or (S)tart over? ');
                  state := CON_STATE_NEW_STATS;
                  end;
   CON_STATE_NEW_STATS: begin
                  if (length(argument) =0) then
                    begin
                    send(#13#10'Do you wish to (C)ontinue, (R)eroll or (S)tart over? ');
                    exit;
                    end;

                  case (upcase(argument[1])) of
                    'C':begin
                        digest := ch.md5_password;

                        ch.load(ch.name);
                        ch.md5_password := digest;
                        ch.save(ch.name);

                        send(#13#10'Thank you. You have completed your entry.'#13#10);

                        send(ch.ansiColor(2) + #13#10);

                        if (ch.IS_IMMORT) then
                          send(ch.ansiColor(2) + #13#10 + findHelp('IMOTD').text)
                        else
                          send(ch.ansiColor(2) + #13#10 + findHelp('MOTD').text);

                        send('Press Enter.'#13#10);
                        state := CON_STATE_MOTD;
                        end;
                    'R':begin
                        with ch do
                          begin
                          top:=250;

                          str:=URange(25,random(UMax(top,75))+ch.race.str_bonus,75);
                          dec(top,str);

                          con:=URange(25,random(UMax(top,75))+ch.race.con_bonus,75);
                          dec(top,con);

                          dex:=URange(25,random(UMax(top,75))+ch.race.dex_bonus,75);
                          dec(top,dex);

                          int:=URange(25,random(UMax(top,75))+ch.race.int_bonus,75);
                          dec(top,int);

                          wis:=URange(25,random(UMax(top,75))+ch.race.wis_bonus,75);
                          dec(top,wis);

                          while (top>0) do
                            begin
                            x:=random(5);

                            case x of
                              0:begin
                                temp:=UMax(75-str,top);
                                if (temp>0) then
                                  begin
                                  dec(top,temp);
                                  str := str + temp;
                                  end;
                                end;
                              1:begin
                                temp:=UMax(75-con,top);
                                if (temp>0) then
                                  begin
                                  dec(top,temp);
                                  con := con + temp;
                                  end;
                                end;
                              2:begin
                                temp:=UMax(75-dex,top);
                                if (temp>0) then
                                  begin
                                  dec(top,temp);
                                  dex := dex + temp;
                                  end;
                                end;
                              3:begin
                                temp:=UMax(75-int,top);
                                if (temp>0) then
                                  begin
                                  dec(top,temp);
                                  int := int + temp;
                                  end;
                                end;
                              4:begin
                                temp:=UMax(75-wis,top);
                                if (temp>0) then
                                  begin
                                  dec(top,temp);
                                  wis := wis + temp;
                                  end;
                                end;
                            end;
                            end;

                          //top:=str+con+dex+int+wis;
                          end;

                        send(#13#10'Your character statistics are: '#13#10#13#10);

                        buf := 'Strength:     '+ANSIColor(10,0)+inttostr(ch.str)+ANSIColor(7,0)+#13#10 +
                               'Constitution: '+ANSIColor(10,0)+inttostr(ch.con)+ANSIColor(7,0)+#13#10 +
                               'Dexterity:    '+ANSIColor(10,0)+inttostr(ch.dex)+ANSIColor(7,0)+#13#10 +
                               'Intelligence: '+ANSIColor(10,0)+inttostr(ch.int)+ANSIColor(7,0)+#13#10 +
                               'Wisdom:       '+ANSIColor(10,0)+inttostr(ch.wis)+ANSIColor(7,0)+#13#10;
                        send(buf);

                        send(#13#10'Do you wish to (C)ontinue, (R)eroll or (S)tart over? ');
                        end;
                    'S':begin
                        send(#13#10'Very well, restarting.'#13#10);
                        send('By what name do you wish to be known?');
                        state := CON_STATE_NEW_NAME;
                        end;
                  else
                    send('Do you wish to (C)ontinue, (R)eroll or (S)art over? ');
                    exit;
                 end;
                 end;
    else
      bugreport('nanny', 'mudthread.pas', 'illegal state');
  end;
end;

procedure GPlayerConnection.writePager(const txt : string);
begin
  if (pagepoint = 0) then
    begin
    pagepoint := 1;
    pagecmd:=#0;
    end;

  pagebuf := pagebuf + txt;
end;

procedure GPlayerConnection.setPagerInput(argument : string);
begin
  argument := trim(argument);

  if (length(argument) > 0) then
    pagecmd := argument[1];
end;

procedure GPlayerConnection.outputPager();
var 
	c : GPlayer;
	last, pclines, lines : integer;
	buf : string;
begin
  if (pagepoint = 0) then
    exit;

{  if (original <> nil) then
    c := original
  else
    c := ch; }
  c := ch;

  pclines := UMax(c.pagerlen, 5) - 2;

  c.emptyBuffer;
  
  if (pagecmd <> #0) then
    send(#13#10);
    
  case pagecmd of
    'b':lines:=-1-(pclines*2);
    'r':lines:=-1-pclines;
    'q':begin
        ch.sendBuffer(' ');
        pagepoint := 0;
        pagebuf := '';
        exit;
        end;
  else
    lines:=0;
  end;

  while (lines<0) and (pagepoint >= 1) do
    begin
    if (pagebuf[pagepoint] = #13) then
      inc(lines);

    pagepoint := pagepoint - 1;
    end;

  if (pagepoint < 1) then
    pagepoint := 1;

  lines := 0;
  last := pagepoint;

  while (lines < pclines) and (last <= length(pagebuf)) do
    begin
    if (pagebuf[last] = #0) then
      break
    else
    if (pagebuf[last] = #13) then
      inc(lines);

    inc(last);
    end;

  if (last <= length(pagebuf)) and (pagebuf[last] = #10) then
    inc(last);

  if (last <> pagepoint) then
    begin
    buf := copy(pagebuf, pagepoint, last - pagepoint);
    send(buf);
    pagepoint := last;
    end;

  while (last <= length(pagebuf)) and (pagebuf[last] = ' ') do
    inc(last);

  if (last >= length(pagebuf)) then
    begin
    pagepoint := 0;
    c.sendPrompt;
    pagebuf := '';
    exit;
    end;

  pagecmd:=#0;

  send(#13#10'(C)ontinue, (R)efresh, (B)ack, (Q)uit: ');
end;


// GPlayer constructor
constructor GPlayer.Create(conn : GPlayerConnection);
var
	iterator : GIterator;
	chan : GUserChannel;
	tc : GUserChannel;
begin
  inherited Create();

  pagerlen := 25;
  xptogo := round((20 * power(level, 1.2)) * (1 + (random(3) / 10)));

  title := 'the Newbie Adventurer';
  rank := 'an apprentice';
  snooping := nil;
  switching := nil;
  reply := nil;

  cfg_flags := CFG_ASSIST or CFG_BLANK or CFG_ANSI or CFG_AUTOPEEK;
  bankgold := 500;
  clan := nil;
  clanleader := false;
  condition[COND_FULL] := 100;
  condition[COND_THIRST] := 100;
  condition[COND_DRUNK] := 0;
  condition[COND_HIGH] := 0;
  condition[COND_CAFFEINE] := 0;
  logon_first := Now;
  ld_timer := 0;
  edit_buffer := '';
  edit_dest := nil;

  aliases := GDLinkedList.Create();
  skills_learned := GDLinkedList.Create();

  max_skills := 0;
  max_spells := 0;

  pracs := 10; // default for new players(?)

  channels := GDLinkedList.Create();
  iterator := channellist.iterator();
  
  while (iterator.hasNext()) do
    begin
    chan := GUserChannel(iterator.next());
    tc := GUserChannel.Create(chan.channelname);
    channels.insertLast(tc);
    end;
    
	iterator.Free();

  active_board := 1;
  boards[BOARD1] := 0;
  boards[BOARD2] := 0;
  boards[BOARD3] := 0;
  boards[BOARD_NEWS] := 0;
  boards[BOARD_IMM] := 0;

  apb := 7;
  hp := 50 + con + random(11); max_hp:=hp;
  mv := 40 + (dex div 4); max_mv := mv;
  mana := 25; max_mana := 25;
  ac_mod := 0;
  natural_ac := 0;
  hitroll := 50;

  position := POS_STANDING;
  state := STATE_IDLE;
  bash_timer := -2;
  cast_timer := 0;
  bashing := -2;
  mental_state := -10;
  in_command := false;
  
  keylock := false;
  afk := false;

	Self.conn := conn;

  if (IS_GOOD) then
    room := findRoom(ROOM_VNUM_GOOD_PORTAL)
  else
  if (IS_EVIL) then
    room := findRoom(ROOM_VNUM_EVIL_PORTAL);

  fighting := nil;
  
  _fields := GHashTable.Create(PLAYER_FIELDS_HASHSIZE);
end;

// GPlayer destructor
destructor GPlayer.Destroy();
var
	node, node_next : GListNode;
	tc : GUserChannel;
begin
  aliases.clear();
  aliases.Free();

  node := channels.head;
  while (node <> nil) do
    begin
    node_next := node.next;
    tc := GUserChannel(node.element);
    channels.remove(node);
    tc.Free();

    node := node_next;
    end;
 
  channels.clear();
  channels.Free();

  skills_learned.clear();
  skills_learned.Free();
  
  _fields.clear();
  _fields.Free();

  inherited Destroy;
end;

function GPlayer.getField(name : string) : TObject;
begin
	if (_fields = nil) then
		Result := GPlayerField(fieldList[name]).default()
	else
		begin
		name := prep(name);

		if (_fields[name] = nil) then
			_fields[name] := GPlayerField(fieldList[name]).default();

		Result := _fields[name];
		end;
end;

procedure GPlayer.putField(name : string; obj : TObject);
begin
	if (_fields = nil) then
		exit;

  name := prep(name);

	if (_fields[name] <> nil) then
		begin
		_fields[name].Free();
		_fields.remove(name);
		end;
	
	_fields[name] := obj;
end;

// Quit procedure
procedure GPlayer.quit();
var
	vict : GCharacter;
	iterator : GIterator;
begin
  raiseEvent('char-logout', Self);

  emptyBuffer();

  if (conn = nil) then
    writeConsole('(Linkless) '+ name + ' has logged out')
  else
  if (conn <> nil) then
    writeConsole('(' + IntToStr(conn.socket.getDescriptor) + ') ' + name + ' has logged out');

  { switched check}
  if (conn <> nil) and (not IS_NPC) then
    begin
    conn.state := CON_STATE_LOGGED_OUT;

    try
      conn.Terminate();
    except
      writeConsole('could not delete thread of ' + name);
    end;

    conn := nil;
    end
  else
  if (not IS_NPC) and (not IS_SET(GPlayer(Self).flags, PLR_LINKLESS)) then
    interpret(Self, 'return sub');

  { perform the cleanup }
  if (snooping <> nil) then
    begin
    snooping.snooped_by := nil;
    snooping := nil;
    end;

  if (switching <> nil) then
    begin
    switching.snooped_by := nil;
    switching := nil;
    end;

  if (leader <> Self) then
    begin
    to_channel(leader, '$B$7[Group]: ' + name + ' has left the group.', CHANNEL_GROUP, AT_WHITE);
    leader := nil;
    end
  else
    begin
    iterator := char_list.iterator();

    while (iterator.hasNext()) do
      begin
      vict := GCharacter(iterator.next());

      if (vict <> Self) and ((vict.leader = Self) or (vict.master = Self)) then
        begin
        act(AT_REPORT,'You stop following $N.',false,vict,nil,Self,TO_CHAR);
        vict.master := nil;
        vict.leader := vict;
        end;
      end;
    
    iterator.Free();
    end;

  save(name);

  extract(true);
end;

function GPlayer.getAge() : integer;
begin
  getAge := 17 + (getPlayed div 1000);
end;

function GPlayer.getPlayed : integer;
begin
  getPlayed := trunc(((played + (Now - logon_now)) * MSecsPerDay) / 60000);
end;

// Player is Immortal
function GPlayer.IS_IMMORT : boolean;
begin
  Result := inherited IS_IMMORT;

  if (level >= LEVEL_IMMORTAL) then
    IS_IMMORT := true;
end;

// Player is wizinvis
function GPlayer.IS_WIZINVIS : boolean;
begin
  Result := IS_SET(flags, PLR_WIZINVIS);
end;

// Player has holywalk
function GPlayer.IS_HOLYWALK : boolean;
begin
  Result := inherited IS_HOLYWALK;

  if (IS_SET(flags, PLR_HOLYWALK)) then
    Result := true;
end;

// Player has holylight
function GPlayer.IS_HOLYLIGHT : boolean;
begin
  Result := inherited IS_HOLYLIGHT;

  if (IS_SET(flags, PLR_HOLYLIGHT)) then
    Result := true;
end;

// Player is AFK
function GPlayer.IS_AFK : boolean;
begin
  if IS_SET(flags, PLR_LINKLESS) then
    IS_AFK := false
  else
    IS_AFK := afk = true;
end;

// Player has a locked keyboard
function GPlayer.IS_KEYLOCKED : boolean;
begin
  if IS_SET(flags, PLR_LINKLESS) then
    IS_KEYLOCKED := false
  else
    IS_KEYLOCKED := keylock = true;
end;

// Player is editing (writing a note)
function GPlayer.IS_EDITING : boolean;
begin
  IS_EDITING := (conn.isEditing());
end;

// Player is drunk
function GPlayer.IS_DRUNK : boolean;
begin
	IS_DRUNK := (condition[COND_DRUNK] > 80);
end;

// Returns nr. of skillslots occupied
function GPlayer.getUsedSkillslots() : integer;
var
  iterator : GIterator;
  g : GLearned;
begin
  Result := 0;
  iterator := skills_learned.iterator();

  while (iterator.hasNext()) do
  	begin
    g := GLearned(iterator.next());
    if (GSkill(g.skill).skill_type <> SKILL_SPELL) then
      inc(Result);
	  end;
	
	iterator.Free();
end;

// Returns nr. of spellslots occupied
function GPlayer.getUsedSpellslots() : integer;
var
  iterator : GIterator;
  g : GLearned;
begin
  Result := 0;
  iterator := skills_learned.iterator();

  while (iterator.hasNext()) do
  	begin
    g := GLearned(iterator.next());
    if (GSkill(g.skill).skill_type = SKILL_SPELL) then
      inc(Result);
	  end;
	
	iterator.Free();
end;

// Load player file
function GPlayer.load(const fn : string) : boolean;
var
	num, d, x : longint;
  af : GFileReader;
  g , a, t : string;
  obj : GObject;
  aff : GAffect;
  len, modif, inner : integer;
  s : string;
  sk : GSkill;
  al : GAlias;
  node : GListNode;
  iterator : GIterator;
  tc : GUserChannel;
begin
  inner := 0;

  level := 1;
  s := fn;
  s[1] := upcase(s[1]);

  try
    af := GFileReader.Create('players\' + fn + '.usr');
  except
    load := false;
    exit;
  end;
  
  _name := hash_string(s);
  _short := hash_string(s + ' is here');
  _long := hash_string(s + ' is standing here');

  repeat
    repeat
      s := af.readLine;
      s := uppercase(s);
    until (pos('#', s) = 1) or (af.eof);

    if (s = '#PLAYER') then
      begin
      inc(inner);

      a := af.readLine();
      
      while (a <> '#END') and (not af.eof()) do
      	begin
        g := uppercase(left(a,':'));

				if (g = 'USER') then
				else
				if (g = 'LAST-LOGIN') then
				else
        if (g = 'TITLE') then
          title := right(a,' ')
        else
        if (g ='SEX') then
          sex := strtoint(right(a, ' '))
        else
        if (g = 'RACE') then
          begin
          race := findRace(right(a, ' '));
          
          if (race = nil) then
            begin
            bugreport('GPlayer.load', 'chars.pas', 'Unknown race ' + right(a, ' ') + ', reverting to default instead');
            race := GRace(raceList.head.element);
            end;
          end
        else
        if (g = 'ALIGNMENT') then
          alignment := strtoint(right(a, ' '))
        else
        if (g = 'LEVEL') then
          level := UMin(strtoint(right(a, ' ')), LEVEL_MAX_IMMORTAL)
        else
        if (g = 'AGE') then
          age := strtoint(right(a,' '))
        else
        if (g = 'WEIGHT') then
          weight := strtoint(right(a,' '))
        else
        if (g = 'HEIGHT') then
          height := strtoint(right(a,' '))
        else
        if (g = 'STATS') then
          begin
          a := right(a,' ');
          str := strtoint(left(a,' '));
          a := right(a,' ');
          con := strtoint(left(a,' '));
          a := right(a,' ');
          dex := strtoint(left(a,' '));
          a := right(a,' ');
          int := strtoint(left(a,' '));
          a := right(a,' ');
          wis := strtoint(left(a,' '));
          end
        else
        if (g = 'MAX_SKILLS') then
          max_skills := strtoint(right(a,' '))
        else
        if (g = 'MAX_SPELLS') then
          max_spells := strtoint(right(a,' '))
        else
        if (g = 'PRACTICES') then
          pracs := strtoint(right(a,' '))
        else
        if (g = 'APB') then
          apb := strtoint(right(a,' '))
        else
        if (g = 'MANA') then
          begin
          a := right(a,' ');
          mana := strtoint(left(a,' '));
          a := right(a,' ');
          max_mana := strtoint(left(a,' '));
          end
        else
        if (g = 'HP') then
          begin
          a := right(a,' ');
          hp := strtoint(left(a,' '));
          a := right(a,' ');
          max_hp := strtoint(left(a,' '));
          end
        else
        if (g = 'MV') then
          begin
          a := right(a,' ');
          mv := strtoint(left(a,' '));
          a := right(a,' ');
          max_mv := strtoint(left(a,' '));
          end
        else
        if (g = 'AC') then
          ac := strtoint(right(a,' '))
        else
        if (g = 'HAC') then
          hac := strtoint(right(a,' '))
        else
        if (g = 'BAC') then
          bac := strtoint(right(a,' '))
        else
        if (g = 'AAC') then
          aac := strtoint(right(a,' '))
        else
        if (g = 'LAC') then
          lac := strtoint(right(a,' '))
        else
        if (g = 'GOLD') then
          begin
          a := right(a,' ');
          gold := UMax(strtointdef(left(a, ' '), 0), 0);
          a := right(a,' ');
          bankgold := UMax(strtointdef(left(a, ' '), 0), 0);
          end
        else
        if (g = 'XP') then
          begin
          a := right(a,' ');
          xptot := strtoint(left(a,' '));
          a := right(a,' ');
          xptogo := strtoint(left(a,' '));
          end
        else
        if (g = 'ROOMVNUM') then
          room := findRoom(strtoint(right(a, ' ')))
        else
        if (g = 'KILLS') then
          kills := strtoint(right(a,' '))
        else
        if (g = 'DEATHS') then
          deaths := strtoint(right(a,' '))
        else
        if (g = 'FLAGS') then
          flags := strtoint(right(a,' '))
        else
        if (g = 'CLAN') then
          begin
          clan := findClan(right(a,' '));

          if (clan <> nil) and(clan.leader = name) then
            clanleader := true;
          end
        else
        if (g = 'CONFIG') then
          cfg_flags := strtoint(right(a,' '))
        else
        if (g = 'AC_MOD') then
          ac_mod := strtoint(right(a,' '))
        else
        // for backward compatibility only
        if (g = 'PASSWORD') then
          begin
          password := right(a,' ');
          md5_password := MD5String(password);
          end
        else
        // the new md5 encrypted pwd
        if (g = 'MD5-PASSWORD') then
          begin
          t := right(a,' ');

          d := 1;
          x := 0;

          while (d <= length(t)) do
            begin
            md5_password[x] := strtoint('$' + t[d] + t[d+1]);
            inc(x);
            inc(d, 2);
            end;
          end
        else
        if (g = 'REMORTS') then
          remorts := strtoint(right(a,' '))
        else
        if (g = 'WIMPY') then
          wimpy := strtoint(right(a,' '))
        else
        if (g = 'AFF_FLAGS') then
          aff_flags := strtoint(right(a,' '))
        else
        if (g = 'MENTALSTATE') then
          mental_state := strtoint(right(a,' '))
        else
        if (g = 'CONDITION') then
          begin
          a := right(a,' ');
          condition[COND_DRUNK] := strtoint(left(a,' '));
          a := right(a,' ');
          condition[COND_FULL] := strtoint(left(a,' '));
          a := right(a,' ');
          condition[COND_THIRST] := strtoint(left(a,' '));
          a := right(a,' ');
          condition[COND_CAFFEINE] := strtoint(left(a,' '));
          a := right(a,' ');
          condition[COND_HIGH] := strtoint(left(a,' '));
          end
        else
        if (g = 'AREA') then
          begin
          area_fname := right(a,' ');
          area := findArea(area_fname);
          end
        else
        if (g = 'RANGES') then
          begin
          a := right(a,' ');
          r_lo := strtoint(left(a,' '));
          a := right(a,' ');
          r_hi := strtoint(left(a,' '));
          a := right(a,' ');
          m_lo := strtoint(left(a,' '));
          a := right(a,' ');
          m_hi := strtoint(left(a,' '));
          a := right(a,' ');
          o_lo := strtoint(left(a,' '));
          a := right(a,' ');
          o_hi := strtoint(left(a,' '));
          end
        else
        if (g = 'WIZLEVEL') then
          wiz_level := strtoint(right(a,' '))
        else
        if (g = 'BGPOINTS') then
          bg_points := strtoint(right(a,' '))
        else
        if (g = 'PAGERLEN') then
          pagerlen := strtoint(right(a,' '))
        else
        if (g = 'LOGON') then
          begin
          a := right(a,' ');
          logon_first := strtoint(left(a,' '));
          a := right(a,' ');
          logon_first := logon_first + (strtoint(left(a,' ')) / MSecsPerDay);
          
          if (logon_first = 0) then
            logon_first := Now;
          end
        else
        if (g = 'PLAYED') then
          begin
          a := right(a,' ');
          played := strtoint(left(a,' '));
          a := right(a,' ');
          played := played + (strtoint(left(a,' ')) / MSecsPerDay);
          end
        else
        if (g = 'BAMFIN') then
          bamfin := right(a, ' ')
        else
        if (g = 'BAMFOUT') then
          bamfout := right(a, ' ')
        else
        if (g = 'TAUNT') then
          taunt := right(a, ' ')
        else
        if (g = 'PROMPT') then
          prompt := right(a, ' ')
        else
        if (g = 'ACTIVE_BOARD') then
          active_board := strtoint(right(a,' '))
        else
        if (g = 'READ-NOTES') then
          begin
          a := right(a,' ');
          boards[BOARD1] := strtoint(left(a,' '));
          a := right(a,' ');
          boards[BOARD2] := strtoint(left(a,' '));
          a := right(a,' ');
          boards[BOARD3] := strtoint(left(a,' '));
          a := right(a,' ');
          boards[BOARD_NEWS] := strtoint(left(a,' '));
          a := right(a,' ');
          boards[BOARD_IMM] := strtoint(left(a,' '));
          end
        else
        if (g = 'IGNORE') then
          begin
          iterator := channels.iterator();
          
          while (iterator.hasNext()) do
            begin
            tc := GUserChannel(iterator.next());
            
            if (tc.channelname = right(a, ' ')) then
              tc.ignored := true;
            end;
          end
				else
					begin
					if (fieldList[g] <> nil) then
						_fields[g] := GPlayerField(fieldList[g]).fromString(right(a, ' '))
					else
						writeConsole('Dropping unknown field "' + g + '"');
					end;

        a := af.readLine();
        end;

      if (uppercase(a)='#END') then
        dec(inner);
      end
    else
    if (s = '#SKILLS') then
      begin
      inc(inner);
      repeat
        a := af.readLine();

        if (uppercase(a) <> '#END') and (not af.eof) then
          begin
          a := right(right(a,' '),'''');
          g := left(a,'''');
          a := right(right(a,''''),' ');
          sk := findSkill(g);

          if (sk <> nil) then
            SET_LEARNED(strtointdef(left(a,' '), 0), sk)
          else
            bugreport('GArea.load', 'charlist.pas', 'skill '+g+' does not exist');
          end;
      until (uppercase(a)='#END') or (af.eof);

      if (uppercase(a)='#END') then
        dec(inner);
      end
    else
    if (s = '#AFFECTS') then
      begin
      inc(inner);

      repeat
        a := af.readLine;

        if (uppercase(a) <> '#END') and (not af.eof) then
          begin
          aff := GAffect.Create();

          with aff do
            begin
            a := right(a, '''');

            name := left(a, '''');

            a := right(right(a, ''''), '''');
            
            wear_msg := left(a, '''');

            a := trim(right(a, ''''));

            duration := strtointdef(left(a, ' '), 0);
            len := 1;
            
            while (pos('{', a) > 0) do
              begin
              a := trim(right(a, '{'));

              setLength(modifiers, len);

              modifiers[len - 1].apply_type := findApply(left(a, ' '));

              a := right(a, ' ');

              try
                modif := strtoint(left(a, ' '));
              except
                modif := cardinal(hash_string(left(a, ' ')));
              end;

              modifiers[len - 1].modifier := modif;

              a := trim(right(a, '}'));
              inc(len);
              end;
            end;

          aff.applyTo(Self);
          end;
      until (uppercase(a)='#END') or (af.eof);

      if (uppercase(a)='#END') then
        dec(inner);
      end
    else
    if (s = '#ALIASES') then
      begin
      inc(inner);
      repeat
        a := af.readLine;

        if (uppercase(a) <> '#END') and (not af.eof) then
          begin
          al := GAlias.Create();

          al.alias := left(a, ':');
          al.expand := right(a, ':');

          al.node := aliases.insertLast(al);
          end;
      until (uppercase(a)='#END') or (af.eof);

      if (uppercase(a)='#END') then
        dec(inner);
      end
    else
    if (s = '#OBJECTS') then
      begin
      inc(inner);
      g := af.readLine;

      repeat
        if (uppercase(g) <> '#END') and (not af.eof) then
          begin
          obj := GObject.Create();

				  obj.worn := g;
				  
				  if (obj.worn = 'none') then
				    obj.worn := '';

          with obj do
            begin
            vnum := af.readInteger();
            name := af.readLine();
            short := af.readLine();
            long := af.readLine();

            a := af.readLine;
            item_type :=StrToInt(left(a,' '));
            a := right(a,' ');
            wear_location1 := left(a,' ');
            a := right(a,' ');
            wear_location2 := left(a,' ');

            a := af.readLine;
            value[1] := StrToInt(left(a,' '));
            a := right(a,' ');
            value[2] := StrToInt(left(a,' '));
            a := right(a,' ');
            value[3] := StrToInt(left(a,' '));
            a := right(a,' ');
            value[4] := StrToInt(left(a,' '));

            a := af.readLine;
            weight := StrToInt(left(a,' '));
            a := right(a,' ');
            flags := StrToInt(left(a,' '));
            a := right(a,' ');
            cost := StrToInt(left(a,' '));
            a := right(a, ' ');
            count := strtointdef(left(a, ' '), 1);
            
            if (count = 0) then
              count := 1;

            room := nil;
  
						g := af.readToken();
						
						if (g = 'A') then
							begin
							aff := GAffect.Create();

							aff.name := af.readToken();
							aff.wear_msg := '';

							aff.duration := af.readInteger();
							num := 1;

							while (not af.eol) and (af.readToken() = '{') do
								begin
								setLength(aff.modifiers, num);

								aff.modifiers[num - 1].apply_type := findApply(af.readToken);

								g := af.readToken();

								modif := cardinal(findSkill(g));

								if (modif = 0) then
									modif := strtointdef(g, 0);

								aff.modifiers[num - 1].modifier := modif;

								g := af.readToken();

								inc(num);
								end;
							
							obj.affects.insertLast(aff);

							g := af.readLine();
        			end;
        		end;

					objectList.add(obj);
					            
          obj.toChar(Self);         
          end;
      until (uppercase(g) = '#END') or (af.eof);

      if (uppercase(g) = '#END') then
        dec(inner);
      end
    else
    if (s = '#TROPHY') then
      begin
      inc(inner);
      repeat
        g := af.readLine;

        if (uppercase(g) <> '#END') and (not af.eof) then
          begin
          inc(trophysize);
          g := right(g,' ');
          trophy[trophysize].name := left(g,' ');
          g := right(g,' ');
          trophy[trophysize].level := strtoint(left(g,' '));
          g := right(g,' ');
          trophy[trophysize].times := strtoint(left(g,' '));
          end;
      until (uppercase(g) = '#END') or (af.eof);

      if (uppercase(g) = '#END') then
        dec(inner);
      end;
  until (af.eof);

  af.Free();

  if (inner <> 0) then
    begin
    bugreport('GPlayer.load', 'chars.pas', 'corrupted playerfile ' + name);

    race := GRace(raceList.head.element);
    end;

  if (race <> nil) then
    begin
    save_poison := race.save_poison;
    save_cold := race.save_cold;
    save_para := race.save_para;
    save_breath := race.save_breath;
    save_spell := race.save_spell;
    hitroll := UMax((level div 5)+50,100);
    
    node := race.abilities.head;
    
    while (node <> nil) do
      begin
      SET_LEARNED(100, GSkill(node.element));
      
      node := node.next;
      end;
    end
  else
  	begin
    bugreport('GPlayer.load', 'chars.pas', 'corrupted playerfile ' + name);

    race := GRace(raceList.head.element);
    end;

  if (max_skills = 0) then
    max_skills := race.max_skills;

  if (max_spells = 0) then
    max_spells := race.max_spells;

  calcAC();
  calcRank();
  
  // backwards compatibility fixes
  REMOVE_BIT(aff_flags, AFF_BASHED);
  REMOVE_BIT(aff_flags, AFF_STUNNED);

  load := true;
end;

// Save player
function GPlayer.save(const fn : string) : boolean;
var
	af : GFileWriter;
	temp : TDateTime;
	h : integer;
	obj : GObject;
	al : GAlias;
	g : GLearned;
	aff : GAffect;
	fl : cardinal;
	tc : GUserChannel;
	iterator, inner_iterator : GIterator;
	w1, w2 : string;
	field : GPlayerField;
begin
  if (IS_NPC) then
    begin
    Result := false;
    exit;
    end;

  try
    af := GFileWriter.Create('players\' + fn + '.usr');
  except
    save := false;
    exit;
  end;

	// reset the character to a basic state (without affects) before writing
	iterator := affects.iterator();

	while (iterator.hasNext()) do
		begin
		aff := GAffect(iterator.next());

		aff.modify(Self, false);
		end;

	try
		af.writeLine('#PLAYER');
		af.writeLine('User: ' + name);
		af.writeLine('MD5-Password: ' + MD5Print(md5_password));
		af.writeLine('Sex: ' + IntToStr(sex));
		af.writeLine('Race: ' + race.name);
		af.writeLine('Alignment: ' + IntToStr(alignment));
		af.writeLine('Level: ' + IntToStr(level));
		af.writeLine('Weight: ' + IntToStr(weight));
		af.writeLine('Height: ' + IntToStr(height));
		af.writeLine('aff_flags: ' + IntToStr(aff_flags));
		af.writeLine('Mentalstate: ' + IntToStr(mental_state));
		af.writeLine('Last-login: ' + DateTimeToStr(Now));

		af.writeLine('Title: ' + title);
		af.writeLine('Age: ' + IntToStr(age));
		af.writeLine('Gold: ' + IntToStr(gold) + ' ' + IntToStr(bankgold));
		af.writeLine('XP: ' + IntToStr(xptot) + ' ' + IntToStr(xptogo));
		af.writeLine('Kills: ' + IntToStr(kills));
		af.writeLine('Deaths: ' + IntToStr(deaths));
		af.writeLine('Practices: ' + IntToStr( pracs));
		af.writeLine('Bamfin: ' + bamfin);
		af.writeLine('Bamfout: ' + bamfout);
		af.writeLine('Taunt: ' + taunt);
		af.writeLine('Prompt: ' + prompt);
		af.writeLine('Active_board: ' + IntToStr( active_board));
		af.writeLine('Read-notes: ' + IntToStr(boards[BOARD1]) +  ' ' + IntToStr(boards[BOARD2]) + ' ' + IntToStr(boards[BOARD3]) + ' ' + IntToStr(boards[BOARD_NEWS]) + ' ' + IntToStr(boards[BOARD_IMM]));

		fl := flags;
		REMOVE_BIT(fl, PLR_LINKLESS);
		REMOVE_BIT(fl, PLR_LOADED);

		af.writeLine('Flags: ' + IntToStr(fl));
		af.writeLine('Config: ' + IntToStr(cfg_flags));
		af.writeLine('Remorts: ' + IntToStr(remorts));
		af.writeLine('Wimpy: ' + IntToStr(wimpy));
		af.writeLine('Logon: ' + IntToStr(trunc(logon_first)) + ' ' + IntToStr(trunc(frac(logon_first)*MSecsPerDay)));

		temp:=played + (Now - logon_now);
		af.writeLine('Played: ' + IntToStr(trunc(temp)) + ' ' + IntToStr(trunc(frac(temp)*MSecsPerDay)));

		af.writeLine('Condition: ' + IntToStr(condition[COND_DRUNK]) + ' ' + IntToStr(condition[COND_FULL]) +
						' ' + IntToStr(condition[COND_THIRST]) + ' ' + IntToStr(condition[COND_CAFFEINE]) + ' ' + IntToStr(condition[COND_HIGH]));

		if clan<>nil then
			af.writeLine('Clan: ' + clan.name);

		if area_fname<>'' then
			af.writeLine('Area: ' + area_fname);

		af.writeLine('Ranges: ' + IntToStr(r_lo) + ' ' + IntToStr(r_hi) + ' ' + IntToStr(m_lo) + ' ' + IntToStr(m_hi) + ' ' + IntToStr(o_lo) + ' ' + IntToStr(o_hi));

		af.writeLine('Wizlevel: ' + IntToStr(wiz_level));
		af.writeLine('BGpoints: ' + IntToStr(bg_points));
		af.writeLine('Pagerlen: ' + IntToStr(pagerlen));

		af.writeLine('Stats: ' + IntToStr(str) + ' ' + IntToStr(con) + ' ' + IntToStr(dex) + ' ' + IntToStr(int) + ' ' + IntToStr(wis));

		af.writeLine('Max_skills: ' + IntToStr(max_skills));
		af.writeLine('Max_spells: ' + IntToStr(max_spells));

		af.writeLine('APB: ' + IntToStr(apb));
		af.writeLine('Mana: ' + IntToStr(mana) + ' ' + IntToStr(max_mana));
		af.writeLine('HP: ' + IntToStr(hp) + ' ' + IntToStr(max_hp));
		af.writeLine('Mv: ' + IntToStr(mv) + ' ' + IntToStr(max_mv));
		af.writeLine('AC: ' + IntToStr(ac));
		af.writeLine('HAC: ' + IntToStr(hac));
		af.writeLine('BAC: ' + IntToStr(bac));
		af.writeLine('AAC: ' + IntToStr(aac));
		af.writeLine('LAC: ' + IntToStr(lac));
		af.writeLine('AC_mod: ' + IntToStr(ac_mod));
		af.writeLine('RoomVNum: ' + IntToStr(room.vnum));

		iterator := channels.iterator();

		while (iterator.hasNext()) do
			begin
			tc := GUserChannel(iterator.next());

			if (tc.ignored) then
				af.writeLine('Ignore: ' + tc.channelname);
			end;

		iterator.Free();

		iterator := fieldList.iterator();

		while (iterator.hasNext()) do
			begin
			field := GPlayerField(iterator.next());

			af.writeLine(field.name + ': ' + field.toString(fields[field.name]));
			end;

		iterator.Free();

		af.writeLine('#END');
		af.writeLine('');

		af.writeLine('#SKILLS');

		iterator := skills_learned.iterator();

		while (iterator.hasNext()) do
			begin
			g := GLearned(iterator.next());

			af.writeLine( 'Skill: ''' + GSkill(g.skill).name + ''' ' + IntToStr(g.perc));
			end;
			
		iterator.Free();

		af.writeLine('#END');
		af.writeLine('');

		af.writeLine('#AFFECTS');

		iterator := affects.iterator();

		while (iterator.hasNext()) do
			begin
			aff := GAffect(iterator.next());

			with aff do
				begin
				af.writeString('''' + name + ''' ''' + wear_msg + ''' ');
				af.writeInteger(duration);

				for h := 0 to length(modifiers) - 1 do
					begin
					af.writeString(' { ');

					af.writeString(printApply(modifiers[h].apply_type) + ' ');

					case modifiers[h].apply_type of
						APPLY_STRIPNAME: af.writeString(PString(modifiers[h].modifier)^);
						else
							af.writeInteger(modifiers[h].modifier);
					end;

					af.writeString(' }');
					end;

				af.writeLine('');
				end;
			end;
			
		iterator.Free();

		af.writeLine('#END');
		af.writeLine('');

		af.writeLine( '#ALIASES');

		iterator := aliases.iterator();

		while (iterator.hasNext()) do
			begin
			al := GAlias(iterator.next());

			af.writeLine(al.alias + ':' + al.expand);
			end;
			
		iterator.Free();

		af.writeLine( '#END');
		af.writeLine('');

		af.writeLine('#OBJECTS');

		iterator := inventory.iterator();

		while (iterator.hasNext()) do
			begin
			obj := GObject(iterator.next());

			af.writeLine('none');

			af.writeLine(IntToStr(obj.vnum));

			af.writeLine(obj.name);
			af.writeLine(obj.short);
			af.writeLine(obj.long);
			af.writeLine(IntToStr(obj.item_type) + ' ' + obj.wear_location1 + ' ' + obj.wear_location2);
			af.writeLine(IntToStr(obj.value[1]) + ' ' + IntToStr(obj.value[2]) + ' ' + IntToStr(obj.value[3]) + ' ' + IntToStr(obj.value[4]));
			af.writeLine(IntToStr(obj.weight) + ' ' + IntToStr(obj.flags) + ' ' + IntToStr(obj.cost) + ' ' + IntToStr(obj.count));

			if (obj.affects.size() > 0) then
				begin
				inner_iterator := obj.affects.iterator();
				
				while (inner_iterator.hasNext()) do
					begin
					aff := GAffect(inner_iterator.next());
					
					with aff do
						begin
						af.writeString('A "' + name + '" ');
						af.writeInteger(duration);

						for h := 0 to length(modifiers) - 1 do
							begin
							af.writeString(' { ');

							af.writeString(printApply(modifiers[h].apply_type) + ' ');

							case modifiers[h].apply_type of
								APPLY_STRIPNAME: af.writeString(PString(modifiers[h].modifier)^);
								else
									af.writeInteger(modifiers[h].modifier);
							end;

							af.writeString(' }');
							end;

						af.writeLine('');
						end;
					end;

				inner_iterator.Free();
				end;				
			end;

		iterator.Free();

		iterator := equipment.iterator();

		while (iterator.hasNext()) do
			begin
			obj := GObject(iterator.next());

			af.writeLine(obj.worn);

			af.writeLine(IntToStr(obj.vnum));

			af.writeLine(obj.name);
			af.writeLine(obj.short);
			af.writeLine(obj.long);

			if (obj.wear_location1 = '') then
				w1 := 'none'
			else
				w1 := obj.wear_location1;

			if (obj.wear_location2 = '') then
				w2 := 'none'
			else
				w2 := obj.wear_location2;

			af.writeLine(IntToStr(obj.item_type) + ' ' + w1 + ' ' + w2);
			af.writeLine(IntToStr(obj.value[1]) + ' ' + IntToStr(obj.value[2]) + ' ' + IntToStr(obj.value[3]) + ' ' + IntToStr(obj.value[4]));
			af.writeLine(IntToStr(obj.weight) + ' ' + IntToStr(obj.flags) + ' ' + IntToStr(obj.cost) + ' ' + IntToStr(obj.count));

			if (obj.affects.size() > 0) then
				begin
				inner_iterator := obj.affects.iterator();
				
				while (inner_iterator.hasNext()) do
					begin
					aff := GAffect(inner_iterator.next());
					
					with aff do
						begin
						af.writeString('A "' + name + '" ');
						af.writeInteger(duration);

						for h := 0 to length(modifiers) - 1 do
							begin
							af.writeString(' { ');

							af.writeString(printApply(modifiers[h].apply_type) + ' ');

							case modifiers[h].apply_type of
								APPLY_STRIPNAME: af.writeString(PString(modifiers[h].modifier)^);
								else
									af.writeInteger(modifiers[h].modifier);
							end;

							af.writeString(' }');
							end;

						af.writeLine('');
						end;
					end;
				inner_iterator.Free();
				end;
			end;

		iterator.Free();

		af.writeLine('#END');
		af.writeLine('');

		af.writeLine('#TROPHY');
		for h := 1 to trophysize do
			af.writeLine('Trophy: ' + trophy[h].name + ' ' + IntToStr(trophy[h].level) + ' ' + IntToStr(trophy[h].times));
		af.writeLine('#END');
	finally
		af.Free();
	end;

	// re-apply affects to character
	iterator := affects.iterator();

	while (iterator.hasNext()) do
		begin
		aff := GAffect(iterator.next());

		aff.modify(Self, true);
		end;

  save := true;
end;

procedure GPlayer.sendBuffer(const s : string);
begin
  if (snooped_by <> nil) then
   	begin
   	if (not snooped_by.IS_NPC) then
    	GConnection(GPlayer(snooped_by).conn).send(s); 
    end;

// Xenon 21/Feb/2001: I think someone snooping still wants to see output of his own commands
// Grimlord 18/Aug/2002: A switched player should not, too much clutter
  if (conn = nil) or (not IS_NPC and (switching <> nil)) then
    exit;

  if (IS_EDITING) then
    exit;

	GConnection(conn).writeBuffer(s, in_command);
end;

procedure GPlayer.sendPager(const txt : string);
begin
  if (conn = nil) then
    exit;

  if (IS_NPC) or (not IS_SET(cfg_flags,CFG_PAGER)) then
    sendBuffer(txt)
  else
    conn.writePager(txt);
end;

procedure GPlayer.emptyBuffer();
begin
  if (conn = nil) then
    exit;

	conn.emptyBuffer();
end;

// Start editing mode
procedure GPlayer.startEditing(const text : string);
begin
  if (conn = nil) then
    exit;

	conn.startEditing();

  if (substate = SUB_SUBJECT) then
    begin
    sendBuffer(ansiColor(7) + #13#10 + 'Subject: ');
    exit;
    end;

  GConnection(conn).send(ansiColor(7) + #13#10 + 'Use ~ on a blank line to end. Use .h on a blank line to get help.'#13#10);
  GConnection(conn).send(ansiColor(7) + '----------------------------------------------------------------------'#13#10'> ');

  edit_buffer := text;
  afk := true;
end;

// Return from editing mode
procedure GPlayer.stopEditing();
begin
  sendBuffer('Ok.'#13#10);

  edit_buffer := '';
  substate := SUB_NONE;
  afk := false;
  
  conn.stopEditing();

  sendBuffer('You are now back at your keyboard.'#13#10);
  act(AT_REPORT,'$n has returned to $s keyboard.',false,Self,nil,nil,to_room);
end;

procedure GPlayer.sendEdit(const text : string);
begin
  case substate of
    SUB_NOTE:
      begin
        postNote(Self, text);

        edit_buffer := '';
        substate := SUB_NONE;

        conn.stopEditing();
        afk := false;

        sendBuffer('Note posted.'#13#10);

        act(AT_REPORT,'You are now back at your keyboard.',false,Self,nil,nil,TO_CHAR);
        act(AT_REPORT,'$n finished $s note and is now back at the keyboard.',false,Self,nil,nil,TO_ROOM);

        if (active_board = BOARD_IMM) then
          begin
          act(AT_REPORT,'There is a new note on the ' + board_names[active_board] + ' board.', false, Self, nil, nil, TO_IMM);
          act(AT_REPORT,'Written by ' + name + '.',false,Self,nil,nil,TO_IMM);
          end
        else
          begin
          act(AT_REPORT,'There is a new note on the ' + board_names[active_board] + ' board.', false, Self, nil, nil, TO_ALL);
          act(AT_REPORT,'Written by ' + name + '.', false, Self, nil, nil, TO_ALL);
          end;

        exit;
      end;
    SUB_ROOM_DESC :
      begin
        interpret(Self, 'redit');
        conn.stopEditing();
        afk := false;
        edit_buffer := '';
        substate := SUB_NONE;
        edit_dest := nil;
      end
    else
    begin
      bugreport('GPlayer.sendEdit()', 'chars.pas', 'unrecognized substate');
    end;
  end;
end;

procedure GPlayer.editBuffer(text : string);
begin
  if (conn = nil) then
    exit;

  if (substate = SUB_SUBJECT) then
    begin
    if (length(text) = 0) then
      subject := 'nil'
    else
      subject := text;

    substate := SUB_NOTE;
    startEditing('');
    exit;
    end;

  if (length(text) > 0) and (text[1] = '.') then
	  begin
    text := uppercase(text);
    
    case text[2] of
      'H' :
      	begin
				GConnection(conn).send(ansiColor(7) + '.h  this help' + #13#10);
				GConnection(conn).send(ansiColor(7) + '.c  clear current text' + #13#10);
				GConnection(conn).send(ansiColor(7) + '.v  see current text' + #13#10);
				GConnection(conn).send(ansiColor(7) + '.w  to write and quit' + #13#10);
				GConnection(conn).send(ansiColor(7) + '.q  to quit without writing' + #13#10);
				end;
      'C' :
        begin
				edit_buffer := '';
				GConnection(conn).send(ansiColor(7) + 'Ok, buffer cleared.' + #13#10);
        end;
      'V' :
        begin
				GConnection(conn).send(ansiColor(7) + 'Current text:' + #13#10);
				GConnection(conn).send(ansiColor(7) + '----------------------------------------------------------------------' + #13#10);
				GConnection(conn).send(ansiColor(7) + edit_buffer + #13#10);
        end;
      'W' :
      	begin
	      sendEdit(edit_buffer);
      	end;
      'Q' :
      	begin
      	stopEditing();
      	end;
    
    else
        GConnection(conn).send(ansiColor(7) + 'Enter .h on a blank line for help.' + #13#10);
    end;
    
    sendPrompt();
    exit;
  	end;

  edit_buffer := edit_buffer + text + #13#10;
  GConnection(conn).send(ansiColor(7) + '> ');
end;

function GPlayer.ansiColor(color : integer) : string;
begin
  if (not IS_SET(cfg_flags, CFG_ANSI)) then
    ansiColor := ''
  else
    ansiColor := ansiio.ANSIColor(color, 0);
end;

// Send prompt
procedure GPlayer.sendPrompt();
var
   s, pr, buf : string;
   t : integer;
begin
	if (conn.isEditing()) then
		begin
		conn.send('> ');
		exit;
		end;

	if (conn.pagepoint > 0) then
		exit;

  if (prompt = '') then
    pr := '%hhp %mmv %ama (%l)%t%f> '
  else
    pr := prompt;

  buf := ansiColor(7);

  if (bash_timer > 0) then
    buf := buf +  '[' + inttostr(bash_timer) + '] (Bashed) ';

  if (bashing > 0) then
    buf := buf +  '[' + inttostr(bashing) + '] ';

  if (IS_AFK) then
    buf := buf +  '(AFK) ';

  if (not IS_NPC) then
    begin
    if (substate = SUB_SUBJECT) then
      begin
      conn.send(' ');
      exit;
      end;

    if (conn.isEditing()) then
      begin
      conn.send('> ');
      exit;
      end;

    if (conn.pagepoint > 0) then
      exit;
    end;

  if (hasTimer(Self, TIMER_ACTION) <> nil) then
    buf := buf + '+';

  if (IS_IMMORT) then
    buf := buf + '#' + inttostr(room.vnum) + ' [' + sector_types[room.sector] + '] ';

  if (IS_IMMORT) and (room.areacoords <> nil) then
    buf := buf + room.areacoords.toString() + ' ';
    
  t := 1;
  s := '';

  while (t <= length(pr)) do
    begin
    if (pr[t] = '%') then
      begin
      case pr[t + 1] of
        'h':  s := s + inttostr(hp);
        'H':  s := s + inttostr(max_hp);
        'm':  s := s + inttostr(mv);
        'M':  s := s + inttostr(max_mv);
        'a':  s := s + inttostr(mana);
        'A':  s := s + inttostr(max_mana);
        'l':  s := s + inttostr(level);
        'x':  s := s + inttostr(xptogo);
        'f':  begin
              if (fighting <> nil) and (state = STATE_FIGHTING) then
                begin
                s := s + ' [Oppnt: ';

                with fighting do
                  s := s + hp_perc[UMax(round((hp / max_hp) * 5), 0)];

                s := s + ']';
                end;
              end;
        't':  begin
              if (fighting <> nil) and (position = STATE_FIGHTING) then
               if (fighting.fighting <> nil) and (fighting.fighting <> Self) then
                 begin
                 s := s + ' [' + fighting.fighting.name + ': ';

                 with fighting.fighting do
                   s := s + hp_perc[UMax(round((hp / max_hp) * 5), 0)];

                 s := s + ']';
                 end;
              end;
        else s := s + '%' + pr[t + 1];
      end;

      inc(t);
      end
    else
      s := s + pr[t];

    inc(t);
    end;

  buf := buf + act_color(Self, s, '%') + '> ';

{  if (snooped_by <> nil) then
    begin
    if IS_SET(snooped_by.cfg_flags,CFG_BLANK) then   // Xenon 21/Feb/2001: send extra blank line if config says so
      GConnection(snooped_by.conn).send(#13#10);
    GConnection(snooped_by.conn).send(buf);
    end; }

  if (IS_SET(cfg_flags, CFG_BLANK)) then
		conn.send(#13#10 + buf)
	else  
	  conn.send(buf);
end;

// Player dies
procedure GPlayer.die();
var
   node : GListNode;
begin
  inherited die();

  { when ch died in bg, get him back to room - Grimlord }
  if (bg_status = BG_PARTICIPATE) then
    begin
    hp := max_hp;
    bg_status := BG_NOJOIN;
    fromRoom();
    toRoom(bg_room);
    exit;
    end;

  extract(false);
  hp := 5;
  mana := 0;
  condition[COND_FULL] := 100;
  condition[COND_THIRST] := 100;
  condition[COND_DRUNK] := 0;
  condition[COND_HIGH] := 0;
  condition[COND_CAFFEINE] := 0;
  mv := max_mv;

  while (true) do
    begin
    node := affects.head;

    if (node = nil) then
      break;

    removeAffect(Self, GAffect(node.element));
    end;
end;

// Calculate rank
procedure GPlayer.calcRank();
var
	r : string;
begin
  if level<30 then
    r:='an apprentice'
  else
  if level<60 then
    r:='a student'
  else
  if level<100 then
    r:='a scholar'
  else
  if level<150 then
    r:='knowledgeable'
  else
  if level<200 then
    r:='skilled'
  else
  if level<250 then
    r:='experienced'
  else
  if level<300 then
    r:='well known'
  else
  if level<350 then
    r:='powerful'
  else
  if level<400 then
    r:='brave'
  else
  if level<450 then
    r:='a hero'
  else
  if level<=500 then
    r:='a legend'
  else
    r:='a god';

  rank := r;
end;


// GPlayerField
constructor GPlayerField.Create(const name : string);
begin
	inherited Create();
	
	_name := prep(name);
end;

// GPlayerFieldFlag
function GPlayerFieldFlag.default() : TObject;
begin
	Result := GBitVector.Create(0);
end;

function GPlayerFieldFlag.fromString(const s : string) : TObject;
begin
	Result := GBitVector.Create(StrToIntDef(s, 0));
end;

function GPlayerFieldFlag.toString(x : TObject) : string;
begin
	Result := IntToStr((x as GBitVector).value);
end;

// GPlayerFieldInteger
function GPlayerFieldInteger.default() : TObject;
begin
	Result := GInteger.Create(0);
end;

function GPlayerFieldInteger.fromString(const s : string) : TObject;
begin
	Result := GInteger.Create(StrToIntDef(s, 0));
end;

function GPlayerFieldInteger.toString(x : TObject) : string;
begin
	Result := IntToStr((x as GInteger).value);
end;

// GPlayerFieldString
function GPlayerFieldString.default() : TObject;
begin
	Result := GString.Create('');
end;

function GPlayerFieldString.fromString(const s : string) : TObject;
begin
	Result := GString.Create(s);
end;

function GPlayerFieldString.toString(x : TObject) : string;
begin
	Result := (x as GString).value;
end;

procedure registerField(field : GPlayerField);
begin
	if (fieldList[field.name] <> nil) then
		fieldList.remove(field.name);
		
	fieldList[field.name] := field;
end;

procedure unregisterField(const name : string);
begin
	fieldList.remove(prep(name));
end;

// Find player by name
function findPlayerWorld(ch : GCharacter; name : string) : GCharacter;
var
	iterator : GIterator;
	vict : GCharacter;
	number, count : integer;
begin
  Result := nil;

  number := findNumber(name); // eg 2.char

  if (uppercase(name) = 'SELF') and (not ch.IS_NPC) then
    begin
    Result := ch;
    exit;
    end;

  count := 0;

  iterator := char_list.iterator();

  while (iterator.hasNext()) do
    begin
    vict := GCharacter(iterator.next());

    if ((isName(vict.name,name)) or (isName(vict.short,name))) and (not vict.IS_NPC) then
      begin    
      if (ch <> nil) and (not ch.CAN_SEE(vict)) then
        continue;

      inc(count);

      if (count = number) then
        begin
        Result := vict;
        exit;
        end;
      end;
    end;
    
  iterator.Free();
end;

function findPlayerWorldEx(ch : GCharacter; name : string) : GCharacter;
var
   iterator : GIterator;
   vict : GCharacter;
   number, count : integer;
begin
  Result := nil;

  number := findNumber(name); // eg 2.char

  if (uppercase(name) = 'SELF') and (not ch.IS_NPC) then
    begin
    Result := ch;
    exit;
    end;

  count := 0;

  iterator := char_list.iterator();

  while (iterator.hasNext()) do
    begin
    vict := GCharacter(iterator.next());

    if (lowercase(vict.name) = lowercase(name)) and (not vict.IS_NPC) then
      begin    
      if (ch <> nil) and (not ch.CAN_SEE(vict)) then
        continue;

      inc(count);

      if (count = number) then
        begin
        Result := vict;
        exit;
        end;
      end;
    end;
    
  iterator.Free();
end;

function existsPlayer(const name : string) : boolean;
begin
	Result := FileExists('players\' + name + '.usr');
end;

procedure acceptConnection(list_socket : GSocket);
var
  ac : GSocket;
  conn : GPlayerConnection;
begin
  ac := list_socket.acceptConnection(system_info.lookup_hosts);
  
  ac.setNonBlocking();

  if (isMaskBanned(ac.hostString)) then
    begin
    writeConsole('(' + IntToStr(ac.getDescriptor) + ') Closed banned IP (' + ac.hostString + ')');

    ac.send(system_info.mud_name + #13#10#13#10);
    ac.send('Your site has been banned from this server.'#13#10);
    ac.send('For more information, please mail the administration, ' + system_info.admin_email + '.'#13#10);
    
    ac.Free();
    end
  else
  if (not serverBooted) then
    begin
    ac.send(system_info.mud_name+#13#10#13#10);
    ac.send('Currently, this server is in the process of a reboot.'#13#10);
    ac.send('Please try again later.'#13#10);
    ac.send('For more information, mail the administration, '+system_info.admin_email+'.'#13#10);

    ac.Free();
    end
  else
  if system_info.deny_newconns then
    begin
    ac.send(system_info.mud_name+#13#10#13#10);
    ac.send('Currently, this server is refusing new connections.'#13#10);
    ac.send('Please try again later.'#13#10);
    ac.send('For more information, mail the administration, '+system_info.admin_email+'.'#13#10);

    ac.Free();
    end
  else
  if (connection_list.size() >= system_info.max_conns) then
    begin
    ac.send(system_info.mud_name+#13#10#13#10);
    ac.send('Currently, this server is too busy to accept new connections.'#13#10);
    ac.send('Please try again later.'#13#10);
    ac.send('For more information, mail the administration, '+system_info.admin_email+'.'#13#10);

    ac.Free();
    end
  else
  	begin
  	conn := GPlayerConnection.Create(ac, false, '');
  	conn.Resume();
  	end;
end;

procedure initPlayers();
begin
	fieldList := GHashTable.Create(PLAYER_FIELDS_HASHSIZE);
end;

procedure cleanupPlayers();
begin
	fieldList.clear();
	fieldList.Free();
end;

function playername(from_ch, to_ch : GCharacter) : string;
begin
  if (not to_ch.CAN_SEE(from_ch)) then
    playername := 'someone'
  else
  if (not to_ch.IS_NPC) and (not from_ch.IS_NPC) then
    begin
    if (from_ch.IS_IMMORT) then
      playername := from_ch.name
    else
    if (not to_ch.IS_SAME_ALIGN(from_ch)) then
      begin
      if (from_ch.race.name[1] in ['A','E','O','I','U']) then
        playername := '+* An ' + from_ch.race.name + ' *+'
      else
        playername := '+* A ' + from_ch.race.name + ' *+';
      end
    else
      playername := from_ch.name;
    end
  else
    playername := from_ch.name;

  if (from_ch = to_ch) then
    playername := 'you';
end;

function act_color(to_ch : GCharacter; const acts : string; sep : char) : string;
var
  last, current : integer;
  boldflag : boolean;
  res : string;
begin
  last := 1;
  current := FastCharPos(acts, sep, last);
  boldflag := false;
  res := '';
  
  while (current <> 0) do
    begin
    if (current - last > 0) then
      res := res + CopyStr(acts, last, current - last);

    last := current + 2;
       
    case acts[current + 1] of
        'B': boldflag := true;
        'A': boldflag := false;
   '0'..'9': begin
             if (boldflag) then
               res := res + to_ch.ansiColor(strtoint(acts[current + 1]) + 8)
             else
               res := res + to_ch.ansiColor(strtoint(acts[current + 1])); 
             end;
    end;
    
    current := FastCharPos(acts, sep, last);
    end;
    
  Result := res + CopyStr(acts, last, length(acts) - last + 1)
end;

function act_string(const acts : string; to_ch, ch : GCharacter; arg1, arg2 : pointer) : string;
var
  last, current : integer;
  res, temp : string;
  vch : GCharacter;
  obj1, obj2 : TObject;
  ex : GExit;
begin
  last := 1;
  current := FastCharPos(acts, '$', last);
  res := '';
  vch := arg2;
  obj1 := arg1; obj2 := arg2;

  while (current <> 0) do
    begin
    if (current - last > 0) then
      res := res + CopyStr(acts, last, current - last);

    last := current + 2;
       
		case acts[current + 1] of
			'n': res := res + playername(ch, to_ch);
			'N': begin
					 if (vch = nil) then
						 writeConsole('[BUG]: act() -> vch null')
					 else
						 res := res + playername(vch, to_ch);
					 end;
			'm': res := res + sex_nm[ch.sex];
			'M': begin
					 if (vch = nil) then
						 writeConsole('[BUG]: act() -> vch null')
					 else
						 res := res + sex_nm[vch.sex];
					 end;
			's': res := res + sex_bm[ch.sex];
			'S': begin
					 if (vch = nil) then
						 writeConsole('[BUG]: act() -> vch null')
					 else
						 res := res + sex_bm[vch.sex];
					 end;
			'e': res := res + sex_pm[ch.sex];
			'E': begin
					 if (vch = nil) then
						 writeConsole('[BUG]: act() -> vch null')
					 else
						 res := res + sex_pm[vch.sex];
					 end;
		 'o': begin
					 if (obj1 = nil) then
						 writeConsole('[BUG]: act() -> obj1 null')
					 else
						 res := res + GObject(obj1).name;
					 end;
			'O': begin
					 if (obj2 = nil) then
						 writeConsole('[BUG]: act() -> obj2 null')
					 else
						 res := res + GObject(obj2).name;
					 end;
			'p': begin
					 if (obj1 = nil) then
						 writeConsole('[BUG]: act() -> obj1 null')
					 else
						 res := res + GObject(obj1).short;
					 end;
			'P': begin
					 if (obj2 = nil) then
						 writeConsole('[BUG]: act() -> obj2 null')
					 else
						 res := res + GObject(obj2).short;
					 end;
			't': begin
					 if (arg1 = nil) then
						 writeConsole('[BUG]: act() -> pchar(arg1) null')
					 else
						 res := res + (PString(arg1))^;
					 end;
			'T': begin
					 if (arg2 = nil) then
						 writeConsole('[BUG]: act() -> pchar(arg2) null')
					 else
						 res := res + (PString(arg2))^;
					 end;
			'd': begin
						 if (arg2 = nil) then
							 writeConsole('[BUG]: act() -> arg2 is nil')
						 else
						 begin
							 ex := GExit(arg2);

							 if ((ex.keywords <> nil) and (length(ex.keywords^) = 0)) then
								 res := res + 'door'
							 else
							   begin
								 one_argument(ex.keywords^, temp);
								 res := res + temp;
								 end;
						 end;
					 end;
			else
			  res := res + '$' + acts[current + 1];
		end;
    
    current := FastCharPos(acts, '$', last);
    end;
    
  res := cap(res + CopyStr(acts, last, length(acts) - last + 1));

  Result := act_color(to_ch, res, '$');
end;

procedure act(atype : integer; const acts : string; hideinvis : boolean; ch : GCharacter;
              arg1, arg2 : pointer; typ : integer);
{ Documentation of act routine:

  atype         - Ansi color to start with
  acts          - A string to send
  hideinvis     - Hide action when ch is invisible?
  ch            - Number of character
  arg1, arg2    - Respectively object or character
  type          - Who gets the resulting string:
                      TO_ROOM     = everybody in the room, except ch
                      TO_VICT     = character in vict
                      TO_NOTVICT  = everybody in the room, except ch and vict
                      TO_CHAR     = to ch
                      TO_ALL      = to everyone, except ch
  prompt        - Do we need to send a prompt after the string?
}

function HIDE_VIS(ch, vict : GCharacter) : boolean;
begin
  if (ch = nil) then
    HIDE_VIS := false
  else
    HIDE_VIS := (not ch.CAN_SEE(vict)) and (hideinvis);
end;

var 
	txt : string;
	vch : GCharacter;
	to_ch : GCharacter;
	node : GListNode;
	actList : TList;
	x : integer;

label wind;
begin
  if (length(acts) = 0) then
    exit;

  vch := GCharacter(arg2);

  if (ch = nil) and (typ <> TO_ALL) then
    begin
    writeConsole('[BUG]: act() -> ch null');
    exit;
    end;

  if (typ = TO_CHAR) then
    node := ch.node_world
  else
  if (typ = TO_VICT) then
    begin
    if (vch = nil) then
      begin
      writeConsole('[BUG]: act() -> vch null');
      exit;
      end;

    node := vch.node_world;
    end
  else
  if (typ = TO_ALL) then
    node := char_list.head
  else
  if (typ = TO_ROOM) or (typ = TO_NOTVICT) then
    node := ch.room.chars.head
  else
    node := nil;
    
  actList := TList.Create();

  while (node <> nil) do
    begin
    to_ch := GCharacter(node.element);

    if ((not to_ch.IS_AWAKE) and (to_ch <> ch)) then goto wind;

    if (typ = TO_CHAR) and (to_ch <> ch) then
      goto wind;
    if (typ = TO_VICT) and ((to_ch <> vch) or (to_ch = ch) or (HIDE_VIS(to_ch, ch))) then
      goto wind;
    if (typ = TO_ROOM) and ((to_ch = ch) or (HIDE_VIS(to_ch, ch))) then
      goto wind;
    if (typ = TO_ALL) and (to_ch = ch) then
      goto wind;
    if (typ = TO_NOTVICT) and ((to_ch = ch) or (to_ch = vch)) then
      goto wind;
    if (typ = TO_IMM) and ((to_ch = ch) or (not to_ch.IS_IMMORT)) then
     goto wind;

    txt := act_string(acts, to_ch, ch, arg1, arg2);

    to_ch.sendBuffer(to_ch.ansiColor(atype) + txt + #13#10);

    if (to_ch.IS_NPC) and (to_ch <> ch) then
    	begin
    	if (GNPC(to_ch).context.existsSymbol('onAct')) then
    		actList.add(to_ch);
    	end;

wind:
     if (typ = TO_CHAR) or (typ = TO_VICT) or (typ = TO_IMM) then
       node := nil
     else
     if (typ = TO_ROOM) or (typ = TO_NOTVICT) or (typ = TO_ALL) then
       node := node.next;
     end;

	if (actList.Count > 0) then
		begin
		for x := 0 to actList.Count - 1 do
			begin
			GNPC(actList[x]).context.runSymbol('onAct', [integer(actList[x]), integer(ch), txt]);
			end;
		end;
	
	actList.Free();
end;

end.