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:
  	(N)PC classes & routines
  	
  ## $Id: chars.pas,v 1.14 2004/04/10 22:24:03 druid Exp $
}

unit chars;

interface

uses
    SysUtils,
    Math,
{$IFDEF LINUX}
    Libc,
{$ENDIF}
    area,
    race,
	clan,
    dtypes,
    gvm;


{$M+}
type
    GCharacter = class;

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

    GAlias = class
    public
      alias : string;
      expand : string;

      node : GListNode;
    end;

    GHistoryElement = class
    public
        time : TDateTime;
        contents : PString;
        constructor Create(const txt : string);
        destructor Destroy(); override;
      end;

    GUserChannel = class
    public
			channelname : string;
			history : GDLinkedList;
			ignored : boolean;
			
			constructor Create(const name : string);
			destructor Destroy(); override;
		end;

    GLearned = class
    public
      node : GListNode;

      skill : pointer;
      perc : integer;

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

    {$M+}
    GCharacter = class
      node_world, node_room : GListNode;
      inventory : GDLinkedList;
      equipment : GHashTable;

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

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

      _alignment : integer;
      
      _gold : integer;               { Gold carried }
      _sex : integer;

      _save_poison, _save_cold, _save_para,  { saving throws }
      _save_breath, _save_spell : integer;

      _name, _short, _long : PString;

    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;

      tracking : string;

      logging : boolean;

      position : integer;
      state : integer;
      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;
      race : GRace;
      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? }

      procedure sendPrompt; virtual;
      procedure sendBuffer(const s : string); virtual;
      procedure sendPager(const 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;

      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(const location : string) : GObject;
      function getWield(item_type : integer) : GObject;
      function getDualWield() : GObject;
      procedure affectObject(obj : GObject; remove: boolean);
      function equip(obj : GObject; silent : boolean = false) : boolean;

      procedure die(); virtual;

      procedure setWait(ticks : integer);

      function calcxp2lvl : cardinal;

      procedure calcAC();

      procedure startFlying();
      procedure stopFlying();

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

      constructor Create();
      destructor Destroy(); override;

      procedure setName(const name : string);
      procedure setShortName(const name : string);
      procedure setLongName(const name : string);
      function getName() : string;
      function getShortName() : string;
      function getLongName() : string;
      function getRaceName() : string;

    published
    // properties   
      property level : integer read _level write _level;
      property str : integer read _str write _str;
      property con : integer read _con write _con;
      property dex : integer read _dex write _dex;
      property int : integer read _int write _int;
      property wis : integer read _wis write _wis;

      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 alignment : integer read _alignment write _alignment;

      property gold : integer read _gold write _gold;
      property sex : integer read _sex write _sex;

      property save_poison : integer read _save_poison write _save_poison;
      property save_cold : integer read _save_cold write _save_cold;
      property save_para : integer read _save_para write _save_para;
      property save_breath : integer read _save_breath write _save_breath;
      property save_spell : integer read _save_spell write _save_spell;

      property name : string read getName write setName;
      property short : string read getShortName write setShortName;
      property long : string read getLongName write setLongName;
      property rname : string read getRaceName;
    end;

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

    published
    	constructor Create();
    	destructor Destroy(); override;
    	
      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 sendBuffer(const s : string); override;
      procedure die; override;
    end;
{$M-}


var
   char_list : GDLinkedList;
   extracted_chars : GDLinkedList;


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

procedure cleanExtractedChars();

procedure initChars();
procedure cleanupChars();


implementation


uses
	constants,
    util,
	player,
	conns,
	skills,
	console,
	mudsystem;


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

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

constructor GUserChannel.Create(const name : string);
begin
  inherited Create();
  
  channelname := name;
  history := GDLinkedList.Create();
  ignored := false;
end;

destructor GUserChannel.Destroy();
begin	
  history.clear();
  history.Free();
  
  inherited Destroy();
end;

// GCharacter constructor
constructor GCharacter.Create();
begin
  inherited Create();

  inventory := GDLinkedList.Create();
  equipment := GHashTable.Create(32);
  equipment.setHashFunc(sortedHash);
  affects := GDLinkedList.Create();

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

// GCharacter destructor
destructor GCharacter.Destroy();
begin
  affects.clear();
  affects.Free();

  inventory.clear();
  inventory.Free();
  
  equipment.clear();
  equipment.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 }
begin
  if (CHAR_DIED) then
    begin
    bugreport('extract_char', 'area.pas', 'ch already extracted');
    exit;
    end;

  if (room <> nil) then
    fromRoom();

  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
    { TODO: 
    if (conn <> nil) then
      GConnection(conn).ch := nil; }

    char_list.remove(node_world);
    node_world := extracted_chars.insertLast(Self);
    end;
end;

procedure GCharacter.setName(const name : string);
begin
  _name := hash_string(name);
end;

procedure GCharacter.setShortName(const name : string);
begin
  _short := hash_string(name);
end;

procedure GCharacter.setLongName(const name : string);
begin
  _long := hash_string(name);
end;

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

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

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

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

function GCharacter.getTrust() : integer;
var
   ch : GCharacter;
begin
  if (snooped_by <> nil) and (GPlayer(snooped_by).switching = Self) then
    ch := snooped_by
  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
  iterator : GIterator;
  ch : GCharacter;
begin
  CHAR_DIED := false;

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

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

    if (ch = Self) then
      begin
      CHAR_DIED := true;
      break;
      end;
    end;
  
  iterator.Free();
end;

procedure GCharacter.sendPrompt();
begin
end;

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

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

procedure GCharacter.emptyBuffer();
begin
end;

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

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

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

function GCharacter.IS_AWAKE : boolean;
begin
  IS_AWAKE := (state <> STATE_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 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 := (position = POS_FLYING);
end;

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

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

function GCharacter.IS_OUTSIDE : boolean;
begin
  IS_OUTSIDE := (room.sector <> SECT_INSIDE) and (not room.flags.isBitSet(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
	IS_DRUNK := false;
end;

// Char is wearing an object of type <item_type>
function GCharacter.IS_WEARING(item_type : integer) : boolean;
var
  iterator : GIterator;
  obj : GObject;
begin
  Result := false;
  
  iterator := equipment.iterator();

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

    if (obj.item_type = item_type) then
      begin
      Result := true;
      break;
      end;
    end;
  
  iterator.Free();
end;

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

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

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

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

function GCharacter.IS_EDITING : boolean;
begin
   Result := false;
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 = target) then
    exit;
    
  if (not IS_AWAKE) then
    CAN_SEE := false;

	if (target is GRoom) then
		begin
	  if (room.IS_DARK) and (not IS_HOLYLIGHT) and (not IS_SET(aff_flags, AFF_INFRAVISION)) then
	    CAN_SEE := false;
	  end;

	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;

// Check what percentage char has learned <skill>
function GCharacter.LEARNED(skill : pointer) : integer;
var
	iterator : GIterator;
	g : GLearned;
begin
  Result := 0;

  iterator := skills_learned.iterator();

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

    if (g.skill = skill) then
      begin
      Result := g.perc;
      break;
      end;
    end;
  
  iterator.Free();
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
	iterator : GIterator;
	g, x : GLearned;
begin
	g := nil;
  iterator := skills_learned.iterator();

  while (iterator.hasNext()) do
    begin
    x := GLearned(iterator.next());
    
    if (x.skill = skill) then
      begin
      g := x;
      break;
      end;
    end;
  
  iterator.Free();

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

function GCharacter.ansiColor(color : integer) : string;
begin
  Result := '';
end;

// Char from room
procedure GCharacter.fromRoom();
begin
  if (room = nil) then
    begin
    bugreport('GCharacter.fromRoom', 'chars.pas', 'room null');
    exit;
    end;

  room.chars.remove(node_room);

  if (IS_WEARING(ITEM_LIGHT)) and (room.light > 0) then
    room.light := room.light - 1;

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

  room := nil;
end;

// Char to room
procedure GCharacter.toRoom(to_room : GRoom);
var
	tele : GTeleport;
	iterator : GIterator;
begin
  if (to_room = nil) then
    begin
    bugreport('GCharacter.toRoom', 'chars.pas', 'room null, moving 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');
      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?');

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

  room := to_room;

  if (IS_WEARING(ITEM_LIGHT)) then
    room.light := room.light + 1;

  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 (to_room.flags.isBitSet(ROOM_TELEPORT)) and (to_room.teledelay > 0) then
    begin
    iterator := teleport_list.iterator();

    while (iterator.hasNext()) do
      begin
      tele := GTeleport(iterator.next());
      
      if (tele.t_room = to_room) then
        begin
        iterator.Free();
        exit;
        end;
      end;
      
    iterator.Free();

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

    tele.node := teleport_list.insertLast(tele);
    end;
end;

// Char dies
procedure GCharacter.die();
begin
  { snooping/switching immortals should stop doing so when we die }
	if (snooped_by <> nil) then
    begin
    GPlayer(snooped_by).snooping := nil;
    GPlayer(snooped_by).switching := nil;
    snooped_by.sendBuffer('Ok.'#13#10);
    snooped_by := nil;
    end;

  addCorpse(Self);
end;

// GNPC
constructor GNPC.Create();
begin
	inherited Create();
	
	context := nil;
end;

destructor GNPC.Destroy();
begin
	if (Assigned(context)) then
		FreeAndNil(context);
		
	inherited Destroy();
end;

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

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

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

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

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

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

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

procedure GNPC.die();
begin
  inherited die();

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

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

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

// Get object wearing at bodypart <location>
function GCharacter.getEQ(const location : string) : GObject;
var
	iterator : GIterator;
  obj : GObject;
begin
  Result := nil;

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

    if (obj.worn = location) then
      begin
      Result := obj;
      break;
      end;
    end;
    
  iterator.Free();
end;

// Get wielded object by <item_type>
function GCharacter.getWield(item_type : integer) : GObject;
var
   obj : GOBject;
begin
  getWield := nil;

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

  obj := getEQ('leftwield');
  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('rightwield') <> nil) and (getEQ('leftwield') <> nil) then
    getDualWield := getEQ('leftwield');
end;

// Apply/Remove special affects on an object
procedure GCharacter.affectObject(obj : GObject; remove : boolean);
var
   iterator : GIterator;
   aff : GAffect;
begin
  with obj do
    case obj.item_type of
      ITEM_ARMOR: calcAC;
      ITEM_LIGHT: if (remove) then
                    Self.room.light := room.light - 1
                  else
                    Self.room.light := room.light + 1;
      ITEM_GEM: if (remove) then
                  max_mana := max_mana - obj.value[3]
                else
                  max_mana := max_mana + obj.value[3]
    end;

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

		aff.modify(Self, not remove);
		end;
end;

// Equip object
function GCharacter.equip(obj : GObject; silent : boolean = false) : boolean;
var
  bodypart : GBodyPart;
begin
  Result := 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.wear_location1 <> '') and (getEQ(obj.wear_location1) = nil) then      { Wear on spot #1}
    begin
    bodypart := GBodyPart(race.bodyparts[obj.wear_location1]);
    
    if (bodypart = nil) then
      begin
      act(AT_REPORT, 'You do not have the right anatomy to wear $p.', false, Self, obj, nil, TO_CHAR);
      Result := false;
      exit;
      end;

	if (not silent) then
		begin
		act(AT_REPORT, bodypart.char_message, false, Self, obj, nil, TO_CHAR);
		act(AT_REPORT, bodypart.room_message, false, Self, obj, nil, TO_ROOM);
		end;

    obj.fromChar();
    obj.worn := obj.wear_location1;
    obj.toChar(Self);

    affectObject(obj, false);
    end
  else
  if (obj.wear_location2 <> '') and (getEQ(obj.wear_location2) = nil) then      { Wear on spot #2}
    begin
    bodypart := GBodyPart(race.bodyparts[obj.wear_location2]);
    
    if (bodypart = nil) then
      begin
      act(AT_REPORT, 'You do not have the right anatomy to wear $p.', false, Self, obj, nil, TO_CHAR);
      Result := false;
      exit;
      end;
      
	if (not silent) then
		begin
		act(AT_REPORT, bodypart.char_message, false, Self, obj, nil, TO_CHAR);
		act(AT_REPORT, bodypart.room_message, false, Self, obj, nil, TO_ROOM);
		end;
		
    obj.fromChar();
    obj.worn := obj.wear_location2;
    obj.toChar(Self);

    affectObject(obj, false);
    end
  else                                              { No spots left }
    begin
    sendBuffer('You are already wearing something there!'#13#10);
    Result := false;
    end; 
end;

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

// Calculate Armour Class
procedure GCharacter.calcAC();
var
  dex_mod : integer;
  iterator : GIterator;
  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;

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

    if (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;
    end;
    
  iterator.Free();

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

// Start flying
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
    position := POS_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;

// Stop flying
procedure GCharacter.stopFlying();
begin
  if (IS_FLYING) then
    begin
    position := POS_STANDING;

    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;

// Find object in inventory by name
function GCharacter.findInventory(s : string) : GObject;
var 
  obj : GObject;
  iterator : Giterator;
  number, count : integer;
begin
  Result := nil;
  
  number := findNumber(s); // eg 2.object

  count := 0;

  iterator := inventory.iterator();

  while (iterator.hasNext()) do
    begin
    obj := GObject(iterator.next());
    
    if (isObjectName(obj.name, s) or isObjectName(obj.short, s)) then
      begin
      inc(count);
  
      if (count = number) then
        begin
        Result := obj;
        break;
        end;
      end;
    end;
    
  iterator.Free();
end;

// Find object in equipment by name
function GCharacter.findEquipment(const s : string) : GObject;
var 
  obj : GObject;
  iterator : GIterator;
begin
  Result := nil;

  iterator := equipment.iterator();

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

    if (isObjectName(obj.name, s) or isObjectName(obj.short, s)) then
      begin
      Result := obj;
      break;
      end;
    end;
    
  iterator.Free();
end;

{ Added 2.<char> - Nemesis }
function findCharWorld(ch : GCharacter; name : string) : GCharacter;
var
	iterator : GIterator;
	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;

  iterator := char_list.iterator();

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

    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;
        break;
        end;
      end;
    end;
    
	iterator.Free();
end;

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

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

procedure cleanExtractedChars();
begin
  extracted_chars.clear();
end;

procedure initChars();
begin
  char_list := GDLinkedList.Create();
  extracted_chars := GDLinkedList.Create();
  extracted_chars.ownsObjects := false;
end;

procedure cleanupChars();
begin
  char_list.clear();
  char_list.Free();

  extracted_chars.clear();
  extracted_chars.Free();
end;

end.