/
CVS/
boards/CVS/
clans/
gmc/CVS/
help/CVS/
include/CVS/
players/
progs/CVS/
races/CVS/
system/CVS/
text/
text/CVS/
todo/
todo/CVS/
units/CVS/
// $Id: chars.pas,v 1.43 2001/07/12 16:37:01 druid Exp $

unit chars;

interface

uses
    SysUtils,
    Math,
    Winsock2,
    constants,
    strip,
    area,
    race,
    md5,
    ansiio,
    fsys,
    util,
    clan,
    dtypes,
    gvm,
    bulletinboard;


type
    GCharacter = class;

    GTrophy = record
      name : string;
      level, times : integer;
    end;

    GAlias = class
      alias : string;
      expand : string;

      node : GListNode;
    end;

    THistoryElement = // channelhistory stuff
      class
        time : TDateTime;
        contents : PString;
        constructor Create(txt : string);
        destructor Destroy(); override;
      end;

    TChannel =
      class
        channelname : string;
        history : GDLinkedList;
        ignored : boolean;
        constructor Create(txt : string);
        destructor Destroy(); override;
      end;

    GLearned = class
      skill : pointer;
      perc : integer;

      constructor Create(perc_ : integer; skill_ : pointer);
    end;

    {$M+}
    GCharacter = class
      node_world, node_room : GListNode;
      objects : GDLinkedList;

      reply, master, leader : GCharacter;
      fighting , hunting : GCharacter;
      snooped_by : GCharacter;

    private
      _level : integer;
      _str, _con, _dex, _int, _wis : integer;
      _hp, _max_hp : integer;
      _mv, _max_mv : integer;
      _mana, _max_mana : integer;
      _apb : integer;

    public
      ac_mod : integer;             { AC modifier (spells?) }
      natural_ac : integer;         { Natural AC (race based for PC's) }
      hac, bac, aac, lac, ac : integer; { head, body, arm, leg and overall ac }
      hitroll : integer;            { the hit roll }
      damnumdie, damsizedie : integer;
      save_poison, save_cold, save_para,  { saving throws }
      save_breath, save_spell :integer;
      tracking : string;

      logging : boolean;

      position : integer;
      gold : longint;               { Gold carried }
      mental_state : integer;
      room : GRoom;
      substate : integer;
      trust : integer;
      kills : integer;
      wait : integer;
      skills_learned : GDLinkedList;
      cast_timer, bash_timer, bashing : integer;
      in_command : boolean;
      name, short, long : PString;
      sex : integer;
      race : GRace;
      alignment : integer;
      carried_weight : integer;             { weight of items carried }
      weight, height : integer;       { weight/height of (N)PC }
      last_cmd : pointer;
      affects : GDLinkedList;
      aff_flags : cardinal;
      clan : GClan;                 { joined a clan? }
      conn : pointer;

    published
      procedure sendPrompt; virtual;
      procedure sendBuffer(s : string); virtual;
      procedure sendPager(txt : string); virtual;
      procedure emptyBuffer; virtual;

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

      function getTrust : integer;

      function CHAR_DIED : boolean;

      function IS_IMMORT : boolean; virtual;
      function IS_NPC : boolean; virtual;
      function IS_LEARNER : boolean; virtual;
      function IS_AWAKE : boolean; virtual;
      function IS_INVIS : boolean; virtual;
      function IS_HIDDEN : boolean; virtual;
      function IS_WIZINVIS : boolean; virtual;
      function IS_GOOD : boolean; virtual;
      function IS_EVIL : boolean; virtual;
      function IS_SAME_ALIGN(vict : GCharacter) : boolean; virtual;
      function IS_FLYING : boolean; virtual;
      function IS_BANKER : boolean; virtual;
      function IS_SHOPKEEPER : boolean; virtual;
      function IS_OUTSIDE : boolean; virtual;
      function IS_AFFECT(affect : integer) : boolean; virtual;
      function IS_DRUNK : boolean; virtual;
      function IS_WEARING(item_type : integer) : boolean; virtual;
      function IS_HOLYWALK : boolean; virtual;
      function IS_HOLYLIGHT : boolean; virtual;
      function IS_AFK : boolean; virtual;
      function IS_KEYLOCKED : boolean; virtual;
      function IS_EDITING : boolean; virtual;
      function CAN_FLY : boolean; virtual;
      function CAN_SEE(target : TObject) : boolean; virtual;

      function LEARNED(skill : pointer) : integer;
      procedure SET_LEARNED(perc : integer; skill : pointer);

      procedure extract(pull : boolean);
      procedure fromRoom;
      procedure toRoom(to_room : GRoom);

      function getEQ(location : integer) : GObject;
      function getWield(item_type : integer) : GObject;
      function getDualWield : GObject;
      procedure affectObject(obj : GObject; remove: boolean);
      function equip(obj : GObject) : boolean;

      procedure die; virtual;

      procedure setWait(ticks : integer);

      function calcxp2lvl : cardinal;

      procedure calcAC;

      procedure startFlying;
      procedure stopFlying;

      function findInventory(s : string) : GObject;
      function findEquipment(s : string) : GObject;

      constructor Create;
      destructor Destroy; override;

    // properties
      function getName() : string;

      property level : integer read _level write _level;
      property str : integer read _str write _str;
      property con : integer read _str write _str;
      property dex : integer read _str write _str;
      property int : integer read _str write _str;
      property wis : integer read _str write _str;

      property hp : integer read _hp write _hp;
      property max_hp : integer read _max_hp write _max_hp;
      property mv : integer read _mv write _mv;
      property max_mv : integer read _max_mv write _max_mv;
      property mana : integer read _mana write _mana;
      property max_mana : integer read _max_mana write _max_mana;

      property apb : integer read _apb write _apb;

      property pname : string read getName;
    end;

    GNPC = class(GCharacter)
    public
      npc_index : GNPCIndex;
      act_flags : cardinal;
      context : GContext;

    published
      function IS_IMMORT : boolean; override;
      function IS_NPC : boolean; override;
      function IS_LEARNER : boolean; override;
      function IS_WIZINVIS : boolean; override;
      function IS_BANKER : boolean; override;
      function IS_SHOPKEEPER : boolean; override;

      procedure die; override;
    end;

    GPlayer = class(GCharacter)
    public
      edit_buffer : string;
      edit_dest : pointer;

      pagerlen : integer;
      title : string;                     { Title of PC }
      hometown : integer;                { Hometown (vnum of portal) }
      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;
      trophy : array[1..15] of GTrophy;
      trophysize: integer;
      switched : GCharacter;
      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;

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

      constructor Create;
      destructor Destroy; override;

    published
      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 getUsedSkillslots() : integer;       // returns nr. of skillslots occupied
      function getUsedSpellslots() : integer;       // returns nr. of spellslots occupied

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

      function getAge : integer;
      function getPlayed : integer;

      procedure die; override;
      procedure calcRank;

      procedure quit;

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

      procedure startEditing(text : string);
      procedure stopEditing;
      procedure editBuffer(text : string);
      procedure sendEdit(text : string);
    end;
    {$M-}

    GExtractedCharacter = class
      node : GListNode;

      ch : GCharacter;
      pull : boolean;
    end;

var
   char_list : GDLinkedList;
   extracted_chars : GDLinkedList;

function findCharWorld(ch : GCharacter; name : string) : GCharacter;
function findPlayerWorld(ch : GCharacter; name : string) : GPlayer;

procedure cleanChars;

implementation

uses
    conns,
    skills,
    mudsystem,
    mudthread,
    Channels;

constructor THistoryElement.Create(txt : string);
begin
  inherited Create();
  time := Now();
  contents := hash_string(txt);
end;

destructor THistoryElement.Destroy();
begin
  unhash_string(contents);
  inherited Destroy();
end;

constructor TChannel.Create(txt : string);
begin
  inherited Create();
  channelname := txt;
  history := GDLinkedList.Create();
  ignored := false;
end;

destructor TChannel.Destroy();
var
  node : GListNode;
  he : THistoryElement;
begin
  node := history.head;
  while (node <> nil) do
  begin
    he := node.element;
    history.remove(node);
    he.Free();
    node := node.next;
  end;
  history.clean();
  history.Free();
  inherited Destroy();
end;

constructor GCharacter.Create;
begin
  inherited Create;

  objects := GDLinkedList.Create;
  affects := GDLinkedList.Create;

  reply := nil;
  master := nil;
  snooped_by := nil;
  leader := Self;
  tracking := '';
end;

destructor GCharacter.Destroy;
var
   obj : GObject;
   node : GListNode;
   tc : TChannel;
begin
  affects.clean;
  affects.Free;

  if (objects.tail <> nil) then
    repeat
      obj := objects.tail.element;
      obj.extract;
    until (objects.tail = nil);

  objects.Free;

  hunting := nil;

  unhash_string(name);
  unhash_string(short);
  unhash_string(long);

  inherited Destroy;
end;

procedure GCharacter.extract(pull : boolean);
{ set pull to false if you wish for character to stay
  alive, e.g. in portal or so. don't set to false for NPCs - Grimlord }
var
   ext : GExtractedCharacter;
begin
  if (CHAR_DIED) then
    begin
    bugreport('extract_char', 'area.pas', 'ch already extracted',
              'Heavy desyncing occured: attempt to extract a character twice.');
    exit;
    end;

  if (room <> nil) then
    fromRoom;

  ext := GExtractedCharacter.Create;
  ext.ch := Self;
  ext.pull := pull;

  ext.node := extracted_chars.insertLast(ext);

  if (not pull) then
    begin
    if (IS_EVIL) then
      toRoom(findRoom(ROOM_VNUM_EVIL_PORTAL))
    else
      toRoom(findRoom(ROOM_VNUM_GOOD_PORTAL));
    end
  else
    begin
    if (conn <> nil) then
      GConnection(conn).ch := nil;

    char_list.remove(node_world);
    end;
end;

function GCharacter.getName() : string;
begin
  if (name <> nil) then
    Result := name^
  else
    Result := '';
end;

function GCharacter.getTrust : integer;
var
   ch : GCharacter;
begin
{  if (conn <> nil) and (GConnection(conn).original <> nil) then
    ch := GConnection(conn).original
  else }
    ch := Self;

  if (ch.trust <> 0) then
    begin
    getTrust := ch.trust;
    exit;
    end;

  if (ch.IS_NPC) then
    getTrust := UMax(ch.level, 500)
  else
    getTrust := ch.level;
end;

function GCharacter.CHAR_DIED : boolean;
var
   ext : GExtractedCharacter;
   node : GListNode;
begin
  CHAR_DIED := false;

  if (Self = nil) then
    begin
    CHAR_DIED := true;
    exit;
    end;

  node := extracted_chars.head;

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

    if (ext.ch = Self) then
      begin
      CHAR_DIED := true;
      exit;
      end;

    node := node.next;
    end;
end;

procedure GCharacter.sendPrompt;
begin
end;

procedure GCharacter.sendBuffer(s : string);
begin
end;

procedure GCharacter.sendPager(txt : string);
begin
end;

procedure GCharacter.emptyBuffer;
begin
end;

// GPlayer
constructor GPlayer.Create;
var
   node : GListNode;
   chan : TChannel;
   tc : TChannel;

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;

  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;

  if (race <> nil) then
    begin
    max_skills := race.max_skills;
    max_spells := race.max_spells;
    end
  else
    begin
    max_skills := 0;
    max_spells := 0;
    end;

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

  channels := GDLinkedList.Create();
  node := channellist.head;

  while (node <> nil) do
    begin
    chan := node.element;
    tc := TChannel.Create(chan.channelname);
    channels.insertLast(tc);
    node := node.next;
    end;

  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;
  bash_timer := -2;
  cast_timer := 0;
  bashing := -2;
  mental_state := -10;
  in_command := false;

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

  fighting := nil;
end;

destructor GPlayer.Destroy;
var
   node, node_next : GListNode;
   tc : TChannel;
begin
  aliases.clean;
  aliases.Free;

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

    node := node_next;
    end;

  channels.clean();
  channels.Free();

  inherited Destroy;
end;

procedure GPlayer.quit;
var
   vict : GCharacter;
   node : GListNode;
   c : GConnection;
begin
  emptyBuffer;

  c := conn;

  if (IS_NPC) then
    begin
    if (c <> nil) then
      c.send('You''re an NPC, you can''t quit!'#13#10);
    exit;
    end;

  if (c = nil) then
    write_console('(Linkless) '+ name^+ ' has logged out')
  else
  if (c <> nil) then
    write_console('(' + inttostr(c.socket) + ') ' + name^ + ' has logged out');

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

    try
      c.thread.terminate;
    except
      write_console('could not delete thread of ' + name^);
    end;

    conn := nil;

    closesocket(c.socket);
    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 (snooped_by <> nil) then
    begin
    snooped_by.player^.snooping := nil;
    snooped_by.sendBuffer('No longer snooping.'#13#10);
    snooped_by := 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
    node := char_list.head;

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

      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;

      node := node.next;
      end;
    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;

function GCharacter.IS_IMMORT : boolean;
begin
  Result := false;
end;

function GNPC.IS_IMMORT : boolean;
begin
  Result := inherited IS_IMMORT;

  if (IS_SET(act_flags, ACT_IMMORTAL)) then
    IS_IMMORT := true;
end;

function GPlayer.IS_IMMORT : boolean;
begin
  Result := inherited IS_IMMORT;

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

function GCharacter.IS_NPC : boolean;
begin
  Result := false;
end;

function GNPC.IS_NPC : boolean;
begin
  Result := true;
end;

function GCharacter.IS_LEARNER : boolean;
begin
  Result := false;
end;

function GNPC.IS_LEARNER : boolean;
begin
  Result := IS_SET(act_flags, ACT_TEACHER);
end;

function GCharacter.IS_AWAKE : boolean;
begin
  IS_AWAKE := (position <> POS_SLEEPING);
end;

function GCharacter.IS_INVIS : boolean;
begin
  IS_INVIS := IS_SET(aff_flags, AFF_INVISIBLE);
end;

function GCharacter.IS_HIDDEN : boolean;
begin
  IS_HIDDEN := IS_SET(aff_flags, AFF_HIDE);
end;

function GCharacter.IS_WIZINVIS : boolean;
begin
  Result := false;
end;

function GNPC.IS_WIZINVIS : boolean;
begin
  Result := IS_SET(act_flags, ACT_MOBINVIS)
end;

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

function GCharacter.IS_GOOD : boolean;
begin
  IS_GOOD := (alignment >= 0) and (not IS_IMMORT);
end;

function GCharacter.IS_EVIL : boolean;
begin
  IS_EVIL := (alignment < 0) and (not IS_IMMORT);
end;

function GCharacter.IS_SAME_ALIGN(vict : GCharacter) : boolean;
begin
  IS_SAME_ALIGN := false;

  if (vict.IS_IMMORT or IS_IMMORT) or (IS_EVIL and vict.IS_EVIL) or
   (IS_GOOD and vict.IS_GOOD) then
    IS_SAME_ALIGN := true;
end;

function GCharacter.IS_FLYING : boolean;
begin
  Result := IS_SET(aff_flags, AFF_FLYING);
end;

function GCharacter.IS_BANKER : boolean;
begin
  Result := false;
end;

function GNPC.IS_BANKER : boolean;
begin
  Result := IS_SET(act_flags, ACT_BANKER);
end;

function GCharacter.IS_SHOPKEEPER : boolean;
begin
  Result := false;
end;

function GNPC.IS_SHOPKEEPER : boolean;
begin
  Result := IS_SET(act_flags, ACT_SHOPKEEP);
end;

function GCharacter.IS_OUTSIDE : boolean;
begin
  IS_OUTSIDE := (room.sector <> SECT_INSIDE) and
               (not IS_SET(room.flags, ROOM_INDOORS));
end;

function GCharacter.IS_AFFECT(affect : integer) : boolean;
begin
  IS_AFFECT := IS_SET(aff_flags, affect);
end;

function GCharacter.IS_DRUNK : boolean;
begin
  if (IS_NPC) then
    IS_DRUNK := false
  else
    IS_DRUNK := (GPlayer(Self).condition[COND_DRUNK] > 80);
end;

function GCharacter.IS_WEARING(item_type : integer) : boolean;
var
   node : GListNode;
   obj : GObject;
begin
  node := objects.head;
  Result := false;

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

    if (obj.wear_location <> WEAR_NULL) and (obj.item_type = item_type) then
      begin
      Result := true;
      break;
      end;

    node := node.next;
    end;
end;

function GCharacter.IS_HOLYWALK : boolean;
begin
  Result := false;
end;

function GPlayer.IS_HOLYWALK : boolean;
begin
  Result := inherited IS_HOLYWALK;

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

function GCharacter.IS_HOLYLIGHT : boolean;
begin
  Result := false;
end;

function GPlayer.IS_HOLYLIGHT : boolean;
begin
  Result := inherited IS_HOLYLIGHT;

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

{ Utility function - Nemesis }
function GCharacter.IS_AFK : boolean;
begin
  Result := false;
end;

function GPlayer.IS_AFK : boolean;
begin
  if IS_SET(flags, PLR_LINKLESS) then
    IS_AFK := false
  else
    IS_AFK := GConnection(conn).afk = true;
end;

{ utility function - Nemesis }
function GCharacter.IS_KEYLOCKED : boolean;
begin
  Result := false;
end;

function GPlayer.IS_KEYLOCKED : boolean;
begin
  if IS_SET(flags, PLR_LINKLESS) then
    IS_KEYLOCKED := false
  else
    IS_KEYLOCKED := GConnection(conn).keylock = true;
end;

function GCharacter.IS_EDITING : boolean;
begin
   Result := false;
end;

function GPlayer.IS_EDITING : boolean;
begin
  IS_EDITING := GConnection(conn).state = CON_EDITING;
end;

function GCharacter.CAN_FLY : boolean;
begin
  Result := false;

  if (IS_SET(aff_flags, AFF_LEVITATION)) then
    Result := true;
end;

{ can ch see ? }
function GCharacter.CAN_SEE(target : TObject) : boolean;
var
  vict : GCharacter;
begin
  CAN_SEE := true;

  if (Self = vict) then
    exit;

  if (not IS_AWAKE) then
    CAN_SEE := false;

  if (room.IS_DARK) and (not IS_HOLYLIGHT) and (not IS_SET(aff_flags, AFF_INFRAVISION)) then
    CAN_SEE := false;

  if (target is GCharacter) then
    begin
    vict := GCharacter(target);

    if (vict.IS_INVIS) and (not (IS_SET(aff_flags, AFF_DETECT_INVIS)
     or IS_IMMORT)) then
      CAN_SEE:=false;

    if (vict.IS_HIDDEN) and (not (IS_SET(aff_flags, AFF_DETECT_HIDDEN)
     or IS_IMMORT)) then
      CAN_SEE := false;

    if (vict.IS_WIZINVIS) and (level < GPlayer(vict).wiz_level) then
      CAN_SEE := false;
    end;

  if (IS_SET(aff_flags, AFF_BLIND)) then
    CAN_SEE := false;
end;

function GCharacter.LEARNED(skill : pointer) : integer;
var
   node : GListNode;
   g : GLearned;
begin
  Result := 0;

  node := skills_learned.head;

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

    if (g.skill = skill) then
      begin
      Result := g.perc;
      break;
      end;

    node := node.next;
    end;
end;

// Xenon 10/Apr/2001: Modified SET_LEARNED() to remove skill from linked list when perc = 0
procedure GCharacter.SET_LEARNED(perc : integer; skill : pointer);
var
   g : GLearned;
   node : GListNode;
begin
  g := nil;
  node := skills_learned.head;

  while (node <> nil) do
    begin
    if (GLearned(node.element).skill = skill) then
      begin
      g := node.element;
      break;
      end;

    node := node.next;
    end;

  if (g = nil) then
    skills_learned.insertLast(GLearned.Create(perc, skill))
  else
    if (perc > 0) then
      g.perc := perc
    else
      skills_learned.remove(node);
end;

function GPlayer.getUsedSkillslots() : integer;       // returns nr. of skillslots occupied
var
  node : GListNode;
  g : GLearned;
begin
  Result := 0;
  node := skills_learned.head;

  while (node <> nil) do
  begin
    g := node.element;
    if (GSkill(g.skill).skill_type <> SKILL_SPELL) then
      inc(Result);
    node := node.next;
  end;
end;

function GPlayer.getUsedSpellslots() : integer;       // returns nr. of spellslots occupied
var
  node : GListNode;
  g : GLearned;
begin
  Result := 0;
  node := skills_learned.head;

  while (node <> nil) do
  begin
    g := node.element;
    if (GSkill(g.skill).skill_type = SKILL_SPELL) then
      inc(Result);
    node := node.next;
  end;
end;

function GPlayer.load(fn : string) : boolean;
var 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;
    chan : GChannel;
    tc : TChannel;
begin
  inner := 0;

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

  name := hash_string(s);
  short := hash_string(s + ' is here');
  long := hash_string(s + ' is standing here');

  if (race <> nil) then
    alignment := race.def_alignment
  else
    alignment := 0;

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

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

    if (s = '#PLAYER') then
      begin
      inc(inner);
      repeat
        a := af.readLine;

        g := uppercase(left(a,':'));

        if (g = 'TITLE') then
          title := right(a,' ')
        else
        if (g ='SEX') then
          sex := strtoint(right(a, ' '))
        else
        if g='RACE' then
          race := findRace(right(a, ' '))
        else
        if (g = 'ALIGNMENT') then
          alignment := strtoint(right(a, ' '))
        else
        if (g = 'LEVEL') then
          level := strtoint(right(a, ' '))
        else
        if g='HOMETOWN' then
          hometown:=strtoint(right(a,' '))
        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='PAGELEN' 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;
      until (uppercase(a)='#END') or (af.eof);

      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',
                      'The skill specified in the pfile 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
            name := hash_string(left(a, ' '));
            wear_msg := 'boe';

            a := 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, ' ');

              modif := cardinal(findSkill(left(a, ' ')));

              if (modif = 0) then
                modif := strtointdef(left(a, ' '), 0);

              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);
      repeat
        g := af.readLine;

        if (uppercase(g) <> '#END') and (not af.eof) then
          begin

          obj := GObject.Create;

          with obj do
            begin
            d := af.readInteger;

            if (d <> -1 ) then
              begin
              obj_index := findObjectIndex(d);

              if (obj_index = nil) then
                bugreport('load_user', 'charlist.pas', 'illegal vnum ' + inttostr(d),
                          'There is no index for this object.')
              else
                inc(obj_index.obj_count);
              end;

            a := af.readLine;
            name := hash_string(a);
            a := af.readLine;
            short := hash_string(a);
            a := af.readLine;
            long := hash_string(a);

            a := af.readLine;
            item_type:=StrToInt(left(a,' '));
            a:=right(a,' ');
            wear1:=StrToInt(left(a,' '));
            a:=right(a,' ');
            wear2:=StrToInt(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;
            end;

          obj.node_world := object_list.insertLast(obj);

          obj.toChar(Self);

          obj.wear_location := strtoint(g);

          if (obj.wear_location < WEAR_NULL) then
            obj.wear_location := WEAR_NULL;
          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('GCharacter.load', 'chars.pas', 'bugged playerfile ' + name^,
              'The pfile of this character was corrupted.');

    race := GRace(race_list.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);
    end;

  if (max_skills = 0) then
  begin
    bugreport('GCharacter.load', 'chars.pas', 'bugged playerfile ' + name^,
              'The pfile of this character lacks a Max_skills field. ' +
              'Fixing this *now* by setting these fields to race-max value. ' +
              'Make sure to (force) save this player.');
    max_skills := race.max_skills;
  end;
  
  if (max_spells = 0) then
  begin
    bugreport('GCharacter.load', 'chars.pas', 'bugged playerfile ' + name^,
              'The pfile of this character lacks a Max_spells field. ' +
              'Fixing this *now* by setting these fields to race-max value. ' +
              'Make sure to (force) save this player.');
    max_spells := race.max_spells;
  end;
  
  calcAC;
  calcRank;

  load := true;
end;

function GPlayer.save(fn : string) : boolean;
var
   f : textfile;
   temp : TDateTime;
   h : integer;
   node : GListNode;
   obj : GObject;
   al : GAlias;
   g : GLearned;
   aff : GAffect;
   fl : cardinal;
begin
  if (IS_NPC) then
    begin
    Result := false;
    exit;
    end;

  assignfile(f, 'players\' + fn + '.usr');

  {$I-}
  rewrite(f);
  {$I+}

  if (IOResult <> 0) then
    begin
    save := false;
    exit;
    end;

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

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

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

  writeln(f,'Flags: ', fl);
  writeln(f,'Config: ',cfg_flags);
  writeln(f,'Remorts: ',remorts);
  writeln(f,'Wimpy: ',wimpy);
  writeln(f,'Logon: ',trunc(logon_first),' ',trunc(frac(logon_first)*MSecsPerDay));

  temp:=played + (Now - logon_now);
  writeln(f,'Played: ',trunc(temp),' ',trunc(frac(temp)*MSecsPerDay));
  writeln(f,'Condition: ',condition[COND_DRUNK],' ',condition[COND_FULL],
          ' ',condition[COND_THIRST],' ',condition[COND_CAFFEINE],' ',condition[COND_HIGH]);

  if clan<>nil then
    writeln(f,'Clan: ',clan.name);

  if area_fname<>'' then
    writeln(f,'Area: ',area_fname);

  writeln(f,'Ranges: ',r_lo,' ',r_hi,' ',m_lo,' ',m_hi,' ',o_lo,' ',o_hi);

  writeln(f,'Wizlevel: ',wiz_level);
  writeln(f,'BGpoints: ',bg_points);
  writeln(f,'Pagerlen: ',pagerlen);

  writeln(f,'Stats: ',str,' ',con,' ',dex,' ',int,' ',wis);

  writeln(f, 'Max_skills: ', max_skills);
  writeln(f, 'Max_spells: ', max_spells);

  writeln(f,'APB: ',apb);
  writeln(f,'Mana: ',mana,' ',max_mana);
  writeln(f,'HP: ',hp,' ',max_hp);
  writeln(f,'Mv: ',mv,' ',max_mv);
  writeln(f,'AC: ',ac);
  writeln(f,'HAC: ',hac);
  writeln(f,'BAC: ',bac);
  writeln(f,'AAC: ',aac);
  writeln(f,'LAC: ',lac);
  writeln(f,'AC_mod: ',ac_mod);
  writeln(f,'RoomVNum: ',room.vnum);
  writeln(f,'#END');
  writeln(f);

  writeln(f,'#SKILLS');
  node := skills_learned.head;;

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

    writeln(f, 'Skill: ''', GSkill(g.skill).name^, ''' ', g.perc);

    node := node.next;
    end;

  writeln(f,'#END');
  writeln(f);

  writeln(f,'#AFFECTS');
  node := affects.head;

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

{    with aff do
      writeln(f,'Affect: ''', skill.name, ''' ',
               printApply(apply_type), ' ', duration, ' ', modifier); }

    node := node.next;
    end;
  writeln(f,'#END');
  writeln(f);

  writeln(f, '#ALIASES');
  node := aliases.head;

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

    writeln(f, al.alias, ':', al.expand);

    node := node.next;
    end;

  writeln(f, '#END');
  writeln(f);

  writeln(f,'#OBJECTS');
  node := objects.head;

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

    writeln(f, obj.wear_location);

    if (obj.obj_index <> nil) then
      writeln(f,obj.obj_index.vnum)
    else
      writeln(f,-1);

    writeln(f,obj.name^);
    writeln(f,obj.short^);
    writeln(f,obj.long^);
    writeln(f,obj.item_type,' ',obj.wear1,' ',obj.wear2,' ');
    writeln(f,obj.value[1],' ',obj.value[2],' ',obj.value[3],' ',obj.value[4]);
    writeln(f,obj.weight,' ',obj.flags,' ',obj.cost, ' ', obj.count);

    node := node.next;
    end;

  writeln(f,'#END');
  writeln(f);

  writeln(f,'#TROPHY');
  for h:=1 to trophysize do
    writeln(f,'Trophy: ',trophy[h].name,' ',trophy[h].level,' ',trophy[h].times);
  writeln(f,'#END');

  closefile(f);

  save := true;
end;

procedure GPlayer.sendBuffer(s : string);
var
   c : GConnection;
begin
  if (snooped_by <> nil) then
    GConnection(snooped_by.conn).send(s);

// Xenon 21/Feb/2001: I think someone snooping still wants to see output of his own commands
  if (conn = nil) {or (not IS_NPC and (player^.snooping <> nil))} then
    exit;

  if (IS_EDITING) then
    exit;

  c := conn;

  if ((length(c.sendbuffer) + length(s)) > 2048) then
    begin
    c.send(c.sendbuffer);
    c.sendbuffer := '';
    end;

  if (not in_command) and (length(c.sendbuffer) = 0) then
    c.sendbuffer := c.sendbuffer + #13#10;

  c.sendbuffer := c.sendbuffer + s;
end;

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

  c := conn;

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

procedure GPlayer.emptyBuffer;
var
   c : GConnection;
begin
  if (conn = nil) then
    exit;

  c := conn;

  if (c.empty_busy) then
    exit;

  c.empty_busy := true;

  if (length(c.sendbuffer) > 0) then
    begin
    c.send(c.sendbuffer);
    sendPrompt;
    c.sendbuffer := '';
    end;

  c.empty_busy := false;
end;

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

  if (substate = SUB_SUBJECT) then
    begin
    sendBuffer(ansiColor(7) + #13#10 + 'Subject: ');
    GConnection(conn).state := CON_EDITING;
    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;
  GConnection(conn).afk := true;
  GConnection(conn).state := CON_EDITING;
end;

procedure GPlayer.stopEditing;
begin
  sendBuffer('Ok.'#13#10);

  edit_buffer := '';
  substate := SUB_NONE;
  GConnection(conn).afk := false;
  GConnection(conn).state := CON_PLAYING;

  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(text : string);
var note : GNote;
begin
  case substate of
    SUB_NOTE:
      begin
        postNote(Self, text);

        edit_buffer := '';
        substate := SUB_NONE;

        GConnection(conn).state := CON_PLAYING;
        GConnection(conn).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');
        GConnection(conn).state := CON_PLAYING;
        GConnection(conn).afk := false;
        edit_buffer := '';
        substate := SUB_NONE;
        edit_dest := nil;
      end
    else
    begin
      bugreport('GCharacter.sendEdit()', 'chars.pas', 'unrecognized substate', 'The substate this character is in has not been recognized.');
    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 (GConnection(conn).state = CON_EDIT_HANDLE) then
    begin
    if (uppercase(text) = 'A') then
      begin
      stopEditing;
      exit;
      end;

    if (uppercase(text) = 'V') then
      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);
      GConnection(conn).send(ansiColor(7) + '(C)ontinue, (V)iew, (S)end or (A)bort? ');
      exit;
      end;

    if (uppercase(text) = 'C') then
      begin
      GConnection(conn).send(ansiColor(7) + 'Ok. Continue writing...' + #13#10);
      GConnection(conn).send(ansiColor(7) + '----------------------------------------------------------------------' + #13#10);
      GConnection(conn).send(ansiColor(7) + edit_buffer);
      GConnection(conn).state := CON_EDITING;
      sendPrompt;
      exit;
      end;

    if (uppercase(text) = 'S') then
      begin
      sendEdit(edit_buffer);
      exit;
      end;

    GConnection(conn).send(#13#10 + ansiColor(7) + '(C)ontinue, (V)iew, (S)end or (A)bort? ');
    exit;
    end;

  if (text = '~') then
    begin
    GConnection(conn).state := CON_EDIT_HANDLE;
    GConnection(conn).send(#13#10 + ansiColor(7) + '(C)ontinue, (V)iew, (S)end or (A)bort? ');
    exit;
    end
  else
  if (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);
        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
    else
      begin
        GConnection(conn).send(ansiColor(7) + 'Enter .h on a blank line for help.' + #13#10);
      end;
    end;
    sendPrompt;
    exit;
  end;

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

function GCharacter.ansiColor(color : integer) : string;
begin
  Result := '';
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;

procedure GPlayer.sendPrompt;
var
   s, pr, buf : string;
   c : GConnection;
   t : integer;
begin
  c := conn;
  if (not IS_NPC) then
    begin
    if (c.state = CON_EDITING) then
      begin
      c.send('> ');
      exit;
      end;

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

  if (prompt = '') then
    pr := '%hhp %mmv %ama (%l)> '
  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
      c.send(' ');
      exit;
      end;

    if (c.state = CON_EDITING) then
      begin
      c.send('> ');
      exit;
      end;

    if (c.state = CON_EDIT_HANDLE) then
      begin
      c.send(' ');
      exit;
      end;

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

  if (position = POS_CASTING) 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(prompt)) do
    begin
    if (prompt[t] = '%') then
      begin
      case prompt[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 (position >= POS_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 >= POS_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 + '%' + prompt[t + 1]; 
      end;

      inc(t);
      end
    else
      s := s + prompt[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
    c.send(#13#10);

  c.send(buf);
end;

procedure GCharacter.fromRoom;
begin
  if (room = nil) then
    begin
    bugreport('GCharacter.fromRoom', 'chars.pas', 'room null',
              'Attempted to remove character from a null room.');
    exit;
    end;

  room.chars.remove(node_room);

  if (IS_WEARING(ITEM_LIGHT)) and (room.light > 0) then
    inc(room.light);

  { Only PCs register as players, so increase the number! - Grimlord }
  if (not IS_NPC) then
    dec(room.area.nplayer);

  room:=nil;
end;

procedure GCharacter.toRoom(to_room : GRoom);
var
   tele : GTeleport;
   node : GListNode;
begin
  if (to_room = nil) then
    begin
    bugreport('GCharacter.toRoom', 'chars.pas', 'room null, moving to portal',
              'Character was forced to re-move to portal.');

    if (IS_IMMORT) then
    begin
      to_room := findRoom(ROOM_VNUM_IMMORTAL_PORTAL);
      if (to_room = nil) then
      begin
        bugreport('GCharacter.toRoom', 'chars.pas', 'immortal portal not found',
                  'This immortal could not be moved to the immortal portal because '+
                  'it doesn''t exit. Immortals get moved to this room when they ' +
                  'logged out in a room that has now been removed.'#13#10 +
                  'To fix this, please create a room with vnum #' + IntToStr(ROOM_VNUM_IMMORTAL_PORTAL) + ', ' +
                  'or change the ROOM_VNUM_IMMORTAL_PORTAL constant in constants.pas to an ' +
                  'existing vnum and recompile Grendel.');
      end;
    end;

    if (to_room = nil) then      
      if (IS_EVIL) then
        to_room := findRoom(ROOM_VNUM_EVIL_PORTAL)
      else
        to_room := findRoom(ROOM_VNUM_GOOD_PORTAL);

    if (to_room = nil) then
      begin
      bugreport('GCharacter.toRoom', 'chars/pas', 'HELP! even portal is NULL room! what did you do?',
                'There are some serious problems with the limbo area! The portal does NOT exist!');

      write_console('System is unstable - prepare for a rough ride');
      exit;
      end;
    end;

  room := to_room;

  if (IS_WEARING(ITEM_LIGHT)) then
    inc(room.light);

  node_room := room.chars.insertLast(Self);

  { Only PCs register as players, so increase the number! - Grimlord }

  if (not IS_NPC) then
    inc(to_room.area.nplayer);

  { check for teleports }
  if (IS_SET(to_room.flags, ROOM_TELEPORT)) and (to_room.teledelay>0) then
    begin
    node := teleport_list.head;

    while (node <> nil) do
      begin
      tele := node.element;
      if (tele.t_room=to_room) then
        exit;

      node := node.next;
      end;

    tele := GTeleport.Create;
    tele.t_room := to_room;
    tele.timer := to_room.teledelay;

    teleport_list.insertLast(tele);
    end;
end;

procedure GCharacter.die;
begin
  addCorpse(Self);
end;

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, node.element);
    end;
end;

procedure GNPC.die;
begin
  inherited die;

  dec(npc_index.count);
  extract(true);
  dec(mobs_loaded);
end;

procedure GCharacter.setWait(ticks : integer);
begin
  wait := UMax(wait, ticks);
end;

function GCharacter.getEQ(location : integer) : GObject;
var
   node : GListNode;
   obj : GObject;
begin
  Result := nil;

  node := objects.head;
  while (node <> nil) do
    begin
    obj := node.element;

    if (obj.wear_location = location) then
      begin
      Result := obj;
      break;
      end;

    node := node.next;
    end;
end;

function GCharacter.getWield(item_type : integer) : GObject;
var
   obj : GOBject;
begin
  getWield := nil;

  obj := getEQ(WEAR_RHAND);
  if (obj <> nil) and (obj.item_type = item_type) then
    begin
    getWield := obj;
    exit;
    end;

  obj := getEQ(WEAR_LHAND);
  if (obj <> nil) and (obj.item_type = item_type) then
    begin
    getWield:=obj;
    exit;
    end;
end;

function GCharacter.getDualWield : GObject;
begin
  getDualWield := nil;

  { can't dual wield }
  if (LEARNED(gsn_dual_wield) = 0) then
    exit;

  if (getEQ(WEAR_RHAND) <> nil) and (getEQ(WEAR_LHAND) <> nil) then
    getDualWield := getEQ(WEAR_LHAND);
end;

procedure GCharacter.affectObject(obj : GObject; remove : boolean);
var
   node : GListNode;
   aff : GAffect;
begin
  with obj do
    case obj.item_type of
      ITEM_ARMOR: calcAC;
      ITEM_LIGHT: if (remove) then
                    dec(Self.room.light)
                  else
                    inc(Self.room.light);
      ITEM_GEM: if (remove) then
                  max_mana := max_mana - obj.value[3]
                else
                  max_mana := max_mana + obj.value[3]
    end;

  if (obj.obj_index <> nil) then
    begin
    node := obj.obj_index.affects.head;

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

      aff.modify(Self, not remove);

      node := node.next;
      end;
    end;
end;

function GCharacter.equip(obj : GObject) : boolean;
const wr_string:array[WEAR_RFINGER..WEAR_EYES, 1..2] of string =
      (('on your finger', 'on $s finger'),
       ('on your finger', 'on $s finger'),
       ('around your neck', 'around $s neck'),
       ('around your neck', 'around $s neck'),
       ('on your body', 'on $s body'),
       ('on your head', 'on $s head'),
       ('on your legs', 'on $s legs'),
       ('on your feet', 'on $s feet'),
       ('on your hands', 'on $s hands'),
       ('on your arms', 'on $s arms'),
       ('as your shield', 'as $s shield'),
       ('about your body', 'about $s body'),
       ('around your waist', 'around $s waist'),
       ('around your right wrist', 'around $s right wrist'),
       ('around your left wrist', 'around $s left wrist'),
       ('near your head', 'near $s head'),
       ('in your hand', 'in $s hand'),
       ('in your hand', 'in $s hand'),
       ('on your shoulder', 'on $s shoulder'),
       ('on your shoulder', 'on $s shoulder'),
       ('on your face', 'on $s face'),
       ('in your ear', 'in $s ear'),
       ('in your ear', 'in $s ear'),
       ('on your ankle', 'on $s ankle'),
       ('on your ankle', 'on $s ankle'),
       ('on your eyes', 'on $s eyes'));
begin
  equip := true;

  if IS_SET(obj.flags,OBJ_ANTI_GOOD) and IS_GOOD then
    begin
    act(AT_REPORT,'You are zapped by $p!',false,Self,obj,nil,TO_CHAR);
    act(AT_REPORT,'$n is zapped by $p and burns $s hands.',false,Self,obj,nil,TO_ROOM);

    obj.fromChar;
    obj.toRoom(room);
    exit;
    end;

  if IS_SET(obj.flags,OBJ_ANTI_EVIL) and IS_EVIL then
    begin
    act(AT_REPORT,'You are zapped by $p!',false,Self,obj,nil,TO_CHAR);
    act(AT_REPORT,'$n is zapped by $p and burns $s hands.',false,Self,obj,nil,TO_ROOM);

    obj.fromChar;
    obj.toRoom(room);
    exit;
    end;

  if (obj.wear1 > 0) and (getEQ(obj.wear1) = nil) then      { Wear on spot #1}
    begin
    act(AT_REPORT,'You wear $p ' + wr_string[obj.wear1, 1] + '.',false, Self, obj, nil, TO_CHAR);
    act(AT_REPORT,'$n wears $p ' + wr_string[obj.wear1, 2] + '.',false, Self, obj, nil, TO_ROOM);
    obj.wear_location := obj.wear1;
    affectObject(obj, false);
    end
  else
  if (obj.wear2 > 0) and (getEQ(obj.wear2) = nil) then      { Wear on spot #2}
    begin
    act(AT_REPORT,'You wear $p ' + wr_string[obj.wear2, 1] + '.',false, Self, obj, nil, TO_CHAR);
    act(AT_REPORT,'$n wears $p ' + wr_string[obj.wear2, 2] + '.',false, Self, obj, nil, TO_ROOM);
    obj.wear_location := obj.wear2;
    affectObject(obj, false);
    end
  else                                              { No spots left }
    begin
    act(AT_REPORT,'You are already wearing something there!',false,Self,nil,nil,TO_CHAR);
    equip := false;
    end;
end;

function GCharacter.calcxp2lvl : cardinal;
begin
  calcxp2lvl := round((20*power(level,1.2))*(1+(random(2)/10)));
end;

procedure GCharacter.calcAC;
var
   dex_mod:integer;
   node : GListNode;
   obj : GObject;
begin
  dex_mod := (dex-50) div 12;
  hac := natural_ac - dex_mod - ac_mod;
  bac := natural_ac - dex_mod - ac_mod;
  aac := natural_ac - dex_mod - ac_mod;
  lac := natural_ac - dex_mod - ac_mod;

  node := objects.head;
  while (node <> nil) do
    begin
    obj := node.element;

    if (obj.wear_location > WEAR_NULL) and (obj.item_type = ITEM_ARMOR) then
      case obj.value[2] of
        ARMOR_HAC : dec(hac, obj.value[3]);
        ARMOR_BAC : dec(bac, obj.value[3]);
        ARMOR_AAC : dec(aac, obj.value[3]);
        ARMOR_LAC : dec(lac, obj.value[3]);
      end;

    node := node.next;
    end;

  ac := (hac + bac + aac + lac) div 4;
end;

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;

procedure GCharacter.startFlying;
begin
  if (not IS_OUTSIDE) then
    begin
    sendBuffer('You cannot fly while indoors!'#13#10);
    exit;
    end;

  if (IS_FLYING) then
    begin
    sendBuffer('You are already flying!'#13#10);
    exit;
    end
  else
  if (CAN_FLY) then
    begin
    SET_BIT(aff_flags, AFF_FLYING);

    act(AT_REPORT,'You begin to fly again!',false,Self,nil,nil,TO_CHAR);
    act(AT_REPORT,'$n gently floats up in the air.',false,Self,nil,nil,TO_ROOM);
    end
  else
    begin
    act(AT_REPORT,'You flap your arms, but never leave the ground.',false,Self,nil,nil,TO_CHAR);
    act(AT_REPORT,'$n flaps $s arms to fly, but can''t.',false,Self,nil,nil,TO_ROOM);
    end;
end;

procedure GCharacter.stopFlying;
begin
  if (IS_FLYING) then
    begin
    REMOVE_BIT(aff_flags, AFF_FLYING);

    act(AT_REPORT,'You slowly land on the ground.',false,Self,nil,nil,TO_CHAR);
    act(AT_REPORT,'$n gently lands on the ground.',false,Self,nil,nil,TO_ROOM);
    end;
end;

function GCharacter.findInventory(s : string) : GObject;
var obj : GObject;
    node : GListNode;
begin
  findInventory := nil;
  node := objects.head;

  while (node <> nil) do
    begin
    obj := node.element;
    if (obj.wear_location = WEAR_NULL) and (isObjectName(obj.name^, s) or isObjectName(obj.short^, s)) then
      begin
      findInventory := obj;
      exit;
      end;

    node := node.next;
    end;
end;

{ Xenon 20/Feb/2001: like findInventory searches thru inv, findEquipment searches thru stuff being worn }
function GCharacter.findEquipment(s : string) : GObject;
var obj : GObject;
    node : GListNode;
begin
  findEquipment := nil;
  node := objects.head;

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

    if (obj.wear_location <> WEAR_NULL) and (isObjectName(obj.name^, s) or isObjectName(obj.short^, s)) then
      begin
      findEquipment := obj;
      exit;
      end;

    node := node.next;
    end;
end;

{ Added 2.<char> - Nemesis }
function findCharWorld(ch : GCharacter; name : string) : GCharacter;
var
   node : GListNode;
   vict : GCharacter;
   number,count : integer;
begin
  findCharWorld := nil;

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

  if (uppercase(name) = 'SELF') then
    begin
    findCharWorld := ch;
    exit;
    end;

  count := 0;

  node := char_list.head;

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

    if (isName(vict.name^,name)) or (isName(vict.short^,name)) and (ch.CAN_SEE(vict)) then
      begin
      inc(count);

      if (count = number) then
        begin
        findCharWorld := vict;
        exit;
        end;
      end;

    node := node.next;
    end;
end;

function findPlayerWorld(ch : GCharacter; name : string) : GPlayer;
var
   node : GListNode;
   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 := GPlayer(ch);
    exit;
    end;

  count := 0;

  node := char_list.head;

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

    if (isName(vict.name^,name)) or (isName(vict.short^,name)) and (ch.CAN_SEE(vict)) and (not ch.IS_NPC) then
      begin
      inc(count);

      if (count = number) then
        begin
        Result := GPlayer(vict);
        exit;
        end;
      end;

    node := node.next;
    end;
end;

procedure cleanChars;
var
   ext : GExtractedCharacter;
   node : GListNode;
begin
  while (true) do
    begin
    node := extracted_chars.tail;

    if (node = nil) then
      exit;

    ext := node.element;

    extracted_chars.remove(node);

    if (ext.pull) then
      ext.ch.Free;

    ext.free;
    end;
end;

{ GLearned }
constructor GLearned.Create(perc_: integer; skill_: pointer);
begin
  inherited Create;

  perc := perc_;
  skill := skill_;
end;

initialization
char_list := GDLinkedList.Create;
extracted_chars := GDLinkedList.Create;

end.