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:
		Area loader & manager
  
  ## $Id: area.pas,v 1.31 2004/04/21 21:42:47 druid Exp $
}

unit area;

interface

uses
	SysUtils,
	Classes,
	constants,
	dtypes,
	clan,
	race,
	fsys,
	gvm;

{$M+}
type
    GRoom = class;
    GShop = class;

    GWeather = record
      mmhg, change, sky : integer;
      temp, temp_mult, temp_avg : integer;
    end;

    GArea = class
    private
      af : GFileReader;
    
      _age : integer;	
      _name, _author : string;
      _maxage : integer;
      _resetmsg : string;
      _flags : GBitVector;

      found_range : boolean;

      _resets : GDLinkedList;
      _rooms : GDLinkedList;
      _objects : GDLinkedList;
      _npcs : GDLinkedList;
      _shops : GDLinkedList;
    public     
      m_lo, m_hi, r_lo, r_hi, o_lo, o_hi : integer;
      fname : string;
      nplayer : integer;
      weather : GWeather;             { current local weather }

      procedure areaBug(const func, problem : string);

      procedure loadRooms();
      procedure loadNPCs();
      procedure loadObjects();
      procedure loadResets();
      procedure loadShops();

      procedure update();
      procedure reset();

      procedure load(const fn : string);
      procedure save(const fn : string);

      constructor Create();
      destructor Destroy(); override;
    published
      property name : string read _name write _name;
      property author : string read _author write _author;
      property resetmsg : string read _resetmsg write _resetmsg;

      property maxage : integer read _maxage write _maxage;
      
      property resets : GDLinkedList read _resets write _resets;
      property rooms : GDLinkedList read _rooms write _rooms;
      property objects : GDLinkedList read _objects write _objects;
      property npcs : GDLinkedList read _npcs write _npcs;
      property shops : GDLinkedList read _shops write _shops;
      
      property flags : GBitVector read _flags write _flags;
    end;

    GObjectValues = array[1..4] of integer;

    GObject = class
    private
      _name, _short, _long : PString;
      _vnum : integer;
    public
      node_room, node_in, node_carry : GListNode;

      area : GArea;
      affects : GDLinkedList;
      contents : GDLinkedList;
      carried_by : pointer;
      in_obj : GObject;
      room : GRoom;
      value : GObjectValues;
      
      worn : string;
      wear_location1, wear_location2 : string;

      flags : cardinal;
      item_type : integer;
      weight : integer;
      cost : integer;
      count : integer;
      timer : integer;
      
      child_count : integer; { how many of me were cloned }

  	published
      procedure toRoom(to_room : GRoom);
      procedure fromRoom();

      procedure toChar(c : pointer);
      procedure fromChar();

      procedure toObject(obj : GObject);
      procedure fromObject();

      function getWeight() : integer;

      function clone() : GObject;
      function group(obj : GObject) : boolean;
      procedure split(num : integer);
      procedure seperate();

      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;

      property vnum : integer read _vnum write _vnum;
      property name : string read getName write setName;
      property short : string read getShortName write setShortName;
      property long : string read getLongName write setLongName;
    end;

    GExit = class
    public
      vnum : integer;
      direction : integer;
      to_room : GRoom;
      keywords : PString;
      flags : cardinal;
      key : integer;
      constructor Create();
    end;

    GNPCIndex = class
    public
      str, con, dex, int, wis : integer;
      hp, mv, mana, apb, natural_ac : integer;
      hitroll : integer;
      damnumdie, damsizedie : integer;
      vnum : integer;
      count : longint;
      name, short, long : PString;
      sex : integer;
      race : GRace;
      alignment : integer;
      level : integer;
      gold, weight, height : integer;

      prog : GCodeBlock;
      progfile : string;

      skills_learned : GDLinkedList;

      act_flags : cardinal;
      area : GArea;
      clan : GClan;
      shop : GShop;

      destructor Destroy; override;
    end;

    GReset = class
    private
    	_reset_type : char;
      _arg1, _arg2, _arg3 : integer;
    	    
    published
    	property reset_type : char read _reset_type write _reset_type;
      property arg1 : integer read _arg1 write _arg1;
      property arg2 : integer read _arg2 write _arg2;
      property arg3 : integer read _arg3 write _arg3;
    end;

    GTeleport = class
    public
    	node : GListNode;
      t_room : GRoom;
      timer : integer;
    end;

    GTrack = class
    public
      who : string;
      life : integer;
      direction : integer;
    end;

    GExtraDescription = class
    public
      keywords : string;
      description : string;
    end;

    GCoords = class  // x: west->east; y: south->north; z: down->up
    public
      x, y, z : integer;
      constructor Create(); overload;
      constructor Create(coords : GCoords); overload;
      function toString() : string;
      procedure copyTo(coords : GCoords);
      procedure copyFrom(coords : GCoords);
    end;

    GRoom = class
    private
      _vnum : integer;
      _name : PString;
      _description : string;
      _sector : integer;
      _televnum, _teledelay : integer;
      _maxlevel, _minlevel : integer;
      _light : integer;
      _height : integer;

    public
      area : GArea;
      areacoords : GCoords;
      worldcoords : GCoords; // not used yet
      extra : GDLinkedList;
      exits : GDLinkedList;
      chars : GDLinkedList;
      objects : GDLinkedList;
      tracks : GDLinkedList;
      flags : GBitVector;

      function IS_DARK : boolean;

      function findChar(c : pointer; name : string) : pointer;
      function findRandomChar : pointer;
      function findRandomGood : pointer;
      function findRandomEvil : pointer;
      function findObject(name : string) : pointer;

      function findDescription(const keyword : string) : GExtraDescription;
      function isConnectedTo(dir : integer) : GRoom;
      function findExit(dir : integer) : GExit;
      function findExitKeyword(s : string) : GExit;

      constructor Create(vn : integer; ar : GArea);
      destructor Destroy; override;

      procedure setName(const name : string);
      function getName() : string;

    published
      property name : string read getName write setName;
      property description : string read _description write _description;
      property vnum : integer read _vnum write _vnum;
      property sector : integer read _sector write _sector;
      property televnum : integer read _televnum write _televnum;
      property teledelay : integer read _teledelay write _teledelay;
      property minlevel : integer read _minlevel write _minlevel;
      property maxlevel : integer read _maxlevel write _maxlevel;
      property light : integer read _light write _light;
      property height : integer read _height write _height;
    end;

    GShop = class
    public
      node : GListNode;
      keeper : integer;                        { keeper vnum }
      area : GArea;
      item_buy : array[1..MAX_TRADE] of integer;    { item_type to buy }
      open_hour, close_hour : integer;          { opening hours }
    end;


var
   objectList : GDLinkedList;  
   objectIndices : GHashTable;
   
   area_list : GDLinkedList;
   room_list : GHashTable;
   teleport_list : GDLinkedList;

   npc_list : GDLinkedList;


procedure resetAreas();
procedure processAreas();
procedure loadAreas();

function createRoom(vnum : integer; area : GArea) : GRoom;
function findArea(const fname : string) : GArea;

function findRoom(vnum : integer) : GRoom;
function findLocation(ch : pointer; const param : string) : GRoom;
function findNPCIndex(vnum : integer) : GNPCIndex;

function instanceNPC(npcindex : GNPCIndex) : pointer;
procedure addCorpse(c : pointer);
function findHeading(s : string) : integer;
function findDirectionShort(startroom, goalroom : GRoom) : string;

function findObjectWorld(s : string) : GObject;

procedure initAreas();
procedure cleanupAreas();

implementation

uses
	strip,
	util,
	chars,
	player,
	skills,
	fight,
	console,
	mudsystem,
	conns;


// GNPCIndex
destructor GNPCIndex.Destroy;
begin
  if (prog <> nil) then
    prog.Free();

  inherited Destroy;
end;

{ GArea constructor }
constructor GArea.Create();
begin
  inherited Create();

  resets := GDLinkedList.Create();
  rooms := GDLinkedList.Create();
  objects := GDLinkedList.Create();
  npcs := GDLinkedList.Create();
  shops := GDLinkedList.Create();

  m_lo := high(integer);
  m_hi := -1;
  r_lo := high(integer);
  r_hi := -1;
  o_lo := high(integer);
  o_hi := -1;

  _author := 'No author';
  _resetmsg := 'No reset';
  _name := 'New area';

  _maxage := 10;
  _age := 0;
  flags := GBitVector.Create(0);

  with weather do
    begin
    mmhg := 1000;
    sky := SKY_CLOUDLESS;
    change := 0;
    temp := 20;
    temp_avg := 20;
    temp_mult := 5;
    end;

  area_list.insertLast(Self);
end;

{ GArea destructor }
destructor GArea.Destroy();
begin
  resets.clear();
  resets.Free();

  rooms.clear();
  rooms.Free();
  
  objects.clear();
  objects.Free();
  
  shops.clear();
  shops.Free();
  
  npcs.clear();
  npcs.Free();

  inherited Destroy();
end;

procedure GArea.areaBug(const func, problem : string);
begin
  bugreport(func, 'area.pas', fname + ': ' + problem + ', line ' + inttostr(af.line));
end;

// Load the rooms
procedure GArea.loadRooms();
var s : string;
    vnum : integer;
    room : GRoom;
    s_exit : GExit;
    s_extra : GExtraDescription;
    buf : string;
    fnd : boolean;
    node : GListNode;
begin
  vnum := 0;

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

    if (uppercase(s) = '#END') then
      exit;

    delete(s, 1, 1);

    try
      vnum := strtoint(left(s, ' '));
    except
      areaBug('loadRooms()', 'invalid numeric format ' + s);
      exit;
    end;

    room := GRoom.Create(vnum, Self);

    with room do
      begin
      s := af.readLine;

      if (pos('#', s) = 1) then
        begin
        areaBug('loadRooms()', 'unexpected new room');
        exit;
        end;

      if (not found_range) then
        begin
        if (vnum < r_lo) then
          r_lo := vnum;
        if (vnum > area.r_hi) then
          r_hi := vnum;
        end;

      _name := hash_string(s);
      buf := '';

      repeat
        s := af.readLine;

        if (s <> '~') then
          buf := buf + s + #13#10;
      until (s = '~');

      _description := buf;

      flags.value := af.readCardinal;
      _minlevel := af.readInteger;
      _maxlevel := af.readInteger;
      _sector := af.readCardinal;

      if (_maxlevel = 0) then
        _maxlevel := LEVEL_MAX;

      if (_sector < 0) or (_sector >= SECT_MAX) then
        areaBug('loadRooms()', 'Sector type mismatch');

      if (flags.isBitSet(ROOM_TELEPORT)) then
        begin
        _televnum := af.readCardinal;
        _teledelay := af.readInteger;
        end;

      while (true) do
        begin
        s := af.readToken;

        if (s = '#END') then
          break;

        case s[1] of
          'S' : break;
          'D' : begin
                s_exit := GExit.Create;
                s_exit.vnum := af.readCardinal;
                s_exit.direction := af.readCardinal;
                s_exit.flags := af.readCardinal;
                s_exit.key := af.readInteger;

                if not (af.eol()) then
                  s_exit.keywords := hash_string(af.readLine)
                else
                  s_exit.keywords := hash_string('');

                if (exits.head = nil) then
                  exits.insertLast(s_exit)
                else
                  begin
                  fnd := false;
                  node := exits.head;

                  while (node <> nil) do
                    begin
                    if (s_exit.direction < GExit(node.element).direction) then
                      begin
                      fnd := true;
                      break;
                      end;

                    node := node.next;
                    end;

                  if (fnd) and (node <> nil) then
                    exits.insertBefore(node, s_exit)
                  else
                    exits.insertLast(s_exit);
                  end;
                end;
          'E' : begin
                s_extra := GExtraDescription.Create;

                s_extra.keywords := af.readLine;
                s_extra.description := '';

                repeat
                  s := trim(af.readLine);

                  if (s <> '~') then
                    s_extra.description := s_extra.description + s + #13#10;
                until (s = '~');

                extra.insertLast(s_extra);
                end;
          end;
        end;
      end;
    
    rooms.add(room);
  until (uppercase(s) = '#END');
end;

procedure GArea.loadNPCs();
var 
	s : string;
	num : integer;
	sk : GSkill;
	npc : GNPCIndex;
	g : GLearned;
//    prog : GProgram;
//    progfile : string;
begin
  npc := nil;
  s := af.readLine;

  repeat
    while (pos('#',s) = 0) do
      s := af.readLine;

    if (uppercase(s)='#END') then
      exit;

    delete(s,1,1);

    try
      num := strtoint(s);

      npc := GNPCIndex.Create();

      npc.prog := nil;
      npc.area := Self;
      npc.skills_learned := GDLinkedList.Create();

      with npc do
        begin
        vnum := num;

        if (not found_range) then
          begin
          if (vnum < area.m_lo) then
            area.m_lo := vnum;
          if (vnum > area.m_hi) then
            area.m_hi := vnum;
          end;

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

        level := af.readCardinal;

        mv := 500;
        
        str := UMin(65 + random(level div 50), 100);
        con := UMin(65 + random(level div 51), 100);
        dex := UMin(65 + random(level div 52), 100);
        int := UMin(65 + random(level div 53), 100);
        wis := UMin(65 + random(level div 54), 100);

        hitroll := UMin((level div 5) + 50, 100);

        hp := (level + 1) * ((con div 4) + random(6) - 3);

        damsizedie := round(sqrt(level));
        damnumdie := round(sqrt(level));

        sex := af.readInteger;

        if (not af.eol()) then
          begin
          s := af.readToken();

          clan := findClan(s);
          end;

        natural_ac := af.readInteger;
        act_flags := af.readCardinal;
        gold := af.readInteger;
        height := af.readInteger;
        weight := af.readInteger;

        s := af.readLine;

        while (pos('>', s) <> 0) or (pos('skill:', s) <> 0) do
          begin
          if (pos('>', s) <> 0) then
            begin
            progfile := 'progs' + PathDelimiter + right(s, ' ');
            prog := loadCode(progfile);
            if (prog = nil) then
              areaBug('loadNPCs()', 'error loading ''' + progfile + '''; file doesn''t exist?');
            end
          else
            begin
            s := right(s,' ');
            sk := findSkill(s);

            if (sk <> nil) then
            	begin
            	g := GLearned.Create(100, sk);
              g.node := skills_learned.insertLast(g);
              end
            else
              areaBug('loadNPCs()', 'unknown skill ' + s);
            end;

          s := af.readLine;
          end;

        if (raceList = nil) or (raceList.head = nil) then
        	race := nil
        else
	        race := GRace(raceList.head.element);

        count := 0;

				npcs.add(npc);
        end;
      except
        areaBug('loadNPCs()', 'Exception while loading mobile section, please check your area');
        npc.Free();
      end;
  until (uppercase(s) = '#END');
end;

// Load the objects
procedure GArea.loadObjects();
var 
  s : string;
  modif, num : integer;
  obj : GObject;
  aff : GAffect;
begin
  num := 0;
  s := af.readLine;

  repeat
    if (uppercase(s) = '#END') then
      exit;

    try
      num := StrToInt(right(s,'#'));
    except
      areaBug('loadObjects()','illegal numeric format ' + s);
      exit;
    end;

    obj := GObject.Create();
    obj.area := Self;

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

      if (not found_range) then
        begin
        if (vnum < area.o_lo) then
          area.o_lo := vnum;
        if (vnum > area.o_hi) then
          area.o_hi := vnum;
        end;

      item_type := af.readInteger;

      wear_location1 := af.readToken();
      wear_location2 := af.readToken();
      
      if (IntToStr(StrToIntDef(wear_location1, 0)) = wear_location1) then
      	writeConsole('hint on line ' + IntToStr(af.line) + ': wear_location1 no longer numeric (now ' + wear_location1 + ')');
      	
      if (IntToStr(StrToIntDef(wear_location2, 0)) = wear_location2) then
      	writeConsole('hint on line ' + IntToStr(af.line) + ': wear_location2 no longer numeric (now ' + wear_location2 + ')');
      
      if (wear_location1 = 'none') then
        wear_location1 := '';

      if (wear_location2 = 'none') then
        wear_location2 := '';

      value[1] := af.readInteger;
      value[2] := af.readInteger;
      value[3] := af.readInteger;
      value[4] := af.readInteger;

      case item_type of
        // if initial condition is set use that, else use max. condition
        ITEM_FOOD : if (value[1] > 0) then
                      timer := value[1]
                    else
                      timer := value[3];
        else
          timer := 0;
      end;

      weight := af.readInteger();
      flags := af.readCardinal();
      cost := af.readInteger();

      s := af.readToken();

      if (s = 'A') then
        begin
        aff := GAffect.Create();

        aff.name := af.readToken();
        aff.wear_msg := '';
        
        aff.duration := af.readInteger();
        num := 1;

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

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

          s := af.readToken();

          modif := cardinal(findSkill(s));

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

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

          s := af.readToken();

          inc(num);
          end;

        affects.insertLast(aff);

        s := af.readLine;
        end;
      end;

		objects.add(obj);
  until (uppercase(s) = '#END');
end;

// Load the resets
procedure GArea.loadResets();
var
  g : GReset;
  d, s : string;
begin
  repeat
    s := af.readLine;

    if (uppercase(s) <> '#END') then
      begin
      g := GReset.Create();

      with g do
        begin
        d := left(s,':');
        reset_type := d[1];

        s := right(s,' ');
        arg1 := strtoint(left(s,' '));
        s := right(s,' ');
        arg2 := strtoint(left(s,' '));
        s := right(s,' ');
        arg3 := strtoint(left(s,' '));
        end;
			
			resets.add(g);
      end;
  until (uppercase(s) = '#END');
end;

// Load the shops
procedure GArea.loadShops();
var
   shop : GShop;
   s : string;
begin
  repeat
    s := af.readLine();

    if (uppercase(s) <> '#END') then
      begin
      shop := GShop.Create();
      shop.area := Self;
      shop.keeper := strtoint(left(s,' '));

      s := af.readLine;

      shop.item_buy[1] := strtoint(left(s,' '));

      s:=right(s,' ');
      shop.item_buy[2] := strtoint(left(s,' '));

      s:=right(s,' ');
      shop.item_buy[3] := strtoint(left(s,' '));

      s:=right(s,' ');
      shop.item_buy[4] := strtoint(left(s,' '));

      s:=right(s,' ');
      shop.item_buy[5] := strtoint(left(s,' '));

      s := af.readLine;
      shop.open_hour := strtoint(left(s,' '));

      s:=right(s,' ');
      shop.close_hour := strtoint(left(s,' '));

      repeat
        s := af.readLine;
      until s='~';

			shops.add(shop);
      end;
  until (uppercase(s) = '#END');
end;

// Load the areafile
procedure GArea.load(const fn : string);
var
  s : string;
begin
  try
    af := GFileReader.Create(fn);
  except
    exit;
  end;

  fname := fn;
  found_range := false;

  repeat
    s := af.readLine;
    s := uppercase(s);

    if (s = '#AREA') then
      begin
      _name := af.readLine;
      _author := af.readLine;
      _resetmsg := af.readLine;
      _maxage := af.readInteger;

      with weather do
        begin
        temp_mult := af.readInteger;
        temp_avg := af.readInteger;
        end;

      flags.value := af.readCardinal;
      _age := 0;
      end
    else
    if (s = '#RANGES') then
      begin
      found_range := true;

      r_lo := af.readInteger;
      r_hi := af.readInteger;
      m_lo := af.readInteger;
      m_hi := af.readInteger;
      o_lo := af.readInteger;
      o_hi := af.readInteger;
      end
    else
    if (s = '#ROOMS') then
      loadRooms()
    else
    if (s = '#MOBILES') then
      loadNPCs()
    else
    if (s = '#OBJECTS') then
      loadObjects()
    else
    if (s = '#RESETS') then
      loadResets()
    else
    if (s = '#SHOPS') then
      loadShops();
  until (s = '$') or (af.eof());

  af.Free();
end;

procedure resetAreas();
var
	area : GArea;
	iterator : GIterator;
begin
	{ reset the areas }
	iterator := area_list.iterator();

	while (iterator.hasNext()) do
		begin
		area := GArea(iterator.next());
		area.reset();
		end;

	iterator.Free();
end;

procedure processAreas();
var
	area : GArea;
	iterator, in_iterator : GIterator;
	node_exit : GListNode;
	to_room, room : GRoom;
	npc : GNPCIndex;
	obj : GObject;
	shop : GShop;
	reset : GReset;
	pexit : GExit;
begin
  { reset the areas }
  iterator := area_list.iterator();

  while (iterator.hasNext()) do
  	begin
    area := GArea(iterator.next());
    
    in_iterator := area.rooms.iterator();
    
    while (in_iterator.hasNext()) do
    	begin
    	room := GRoom(in_iterator.next());
    	
			if (room_list.get(room.vnum) <> nil) then
				bugreport('processAreas()', 'area.pas', 'room #' + IntToStr(room.vnum) + ' defined at least twice')
			else
    		room_list.put(room.vnum, room);
    	end;
    
    in_iterator.Free();

    in_iterator := area.npcs.iterator();
    
    while (in_iterator.hasNext()) do
    	begin
    	npc := GNPCIndex(in_iterator.next());
    	
			npc_list.insertLast(npc);
			end;
		
		in_iterator.Free();

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

    	// Object already exists
    	if (objectIndices[obj.vnum] <> nil) then
    	  begin
    	  bugreport('processAreas()', 'area.pas', 'object #' + IntToStr(obj.vnum) + ' defined at least twice');
    	  exit;
    	  end;
    	
    	objectIndices[obj.vnum] := obj;
			end;
		
		in_iterator.Free();
		
    in_iterator := area.resets.iterator();
    
    while (in_iterator.hasNext()) do
    	begin
    	reset := GReset(in_iterator.next());

			if (reset.reset_type = 'M') then
				begin
				if (findNPCIndex(reset.arg1) = nil) then
					bugreport('processAreas()', 'area.pas', 'M reset npc #' + inttostr(reset.arg1) + ' null');
				end
			else
			if (reset.reset_type = 'O') then
				begin
				if (objectIndices[reset.arg1] = nil) then
					bugreport('processAreas()', 'area.pas', 'O reset obj #' + inttostr(reset.arg1) + ' null');
				end
			else
			if (reset.reset_type = 'E') then
				begin
				if (objectIndices[reset.arg1] = nil) then
					bugreport('processAreas()', 'area.pas', 'E reset obj #' + inttostr(reset.arg1) + ' null');
				end
			else
			if (reset.reset_type = 'I') then
				begin
				if (objectIndices[reset.arg1] = nil) then
					bugreport('processAreas()', 'area.pas', 'I reset obj #' + inttostr(reset.arg1) + ' null');
				end
			else
			if (reset.reset_type = 'G') then
				begin
				if (objectIndices[reset.arg1] = nil) then
					bugreport('processAreas()', 'area.pas', 'G reset obj #' + inttostr(reset.arg1) + ' null');
				end
			else
			if (reset.reset_type = 'D') then
				begin
				if (reset.arg3 < 0) or (reset.arg3 > MAX_DOORTYPE) then
					bugreport('processAreas()', 'area.pas', 'D reset doortype ' + inttostr(reset.arg3) + ' invalid');
				end;
			end;
			
		in_iterator.Free();
		
		in_iterator := area.shops.iterator();
		
		while (in_iterator.hasNext()) do
			begin
			shop := GShop(in_iterator.next());
			
      npc := findNPCIndex(shop.keeper);

      if (npc = nil) then
        bugreport('processAreas()', 'area.pas', 'shopkeeper #'+inttostr(shop.keeper)+' null')
      else
        npc.shop := shop;
			end;
			
		in_iterator.Free();
    end;

	iterator.Free();

  	writeConsole('Checking exits...');

  	{ Checking rooms for errors }

	iterator := room_list.iterator();
	
	while (iterator.hasNext()) do
		begin
		room := GRoom(iterator.next());

		node_exit := room.exits.head;

		while (node_exit <> nil) do
			begin
			pexit := GExit(node_exit.element);

			to_room := findRoom(pexit.vnum);

			if not (pexit.direction in [DIR_NORTH..DIR_SOMEWHERE]) then
				begin
				bugreport('processAreas()', 'area.pas', 'illegal direction ' + IntToStr(pexit.direction) +
									' for exit in room #' + IntToStr(room.vnum));

				room.exits.remove(node_exit);

				node_exit := room.exits.head;
				end
			else
			if (to_room = room) then
				begin
				bugreport('processAreas()', 'area.pas', 'cyclic exit ' + headings[pexit.direction] + ' found in room #' + IntToStr(room.vnum));

				room.exits.remove(node_exit);

				node_exit := room.exits.head;
				end
			else
			if (to_room = nil) then
				begin
				bugreport('processAreas()', 'area.pas', 'exit ' + headings[pexit.direction] +
									' from room #' + IntToStr(room.vnum) + ' to unexisting room #' + IntToStr(pexit.vnum));

				room.exits.remove(node_exit);

				node_exit := room.exits.head;
				end
			else
				begin
				pexit.to_room:=to_room;

				node_exit := node_exit.next;
				end;
			end;
		end;
		
	iterator.Free();
end;

procedure loadAreas();
var
  af : GFileReader;
  s : string;
  area : GArea;
  tm : TDateTime;
begin
  tm := Now();

  try
    af := GFileReader.Create('areas\area.list');
  except
  	exit;
  end;

  repeat
    s := af.readLine();

    if (s <> '$') then
      begin
      area := GArea.Create();
      area.load('areas\' + trim(s));

      s := pad_string(s, 15);

      with area do
        begin
        if (r_lo <> high(integer)) and (r_hi<>-1) then
          s := s + ' R ' + pad_integer(r_lo,5) + '-' + pad_integer(r_hi,5);
        if (m_lo <> high(integer)) and (m_hi<>-1) then
          s := s + ' M ' + pad_integer(m_lo,5) + '-' + pad_integer(m_hi,5);
        if (o_lo <> high(integer)) and (o_hi<>-1) then
          s := s + ' O ' + pad_integer(o_lo,5) + '-' + pad_integer(o_hi,5);
        end;

      writeConsole(s);
      end;
  until (s = '$');

  af.Free();

	processAreas();

  tm := Now() - tm;
  writeConsole('Area loading took ' + FormatDateTime('s "second(s)," z "millisecond(s)"', tm));
end;

{ Xenon 28/Apr/2001 : added saving of #RANGES; fixed bug that caused areas
                      not to save (and their length set to 0) }
procedure GArea.save(const fn : string);
var
   f : textfile;
   g : GLearned;
   node_ex : GListNode;
   ex : GExit;
   extra : GExtraDescription;
   room : GRoom;
   npcindex : GNPCIndex;
   reset : GReset;
   iterator : GIterator;
   shop : GShop;
   obj : GObject;
begin
  assign(f, fn);
  {$I-}
  rewrite(f);
  {$I+}

  if (IOResult <> 0) then
    begin
    bugreport('GArea.save', 'area.pas', 'Could not open ' + fn + '!');
    exit;
    end;

  writeln(f, '#RANGES');
  writeln(f, Format('%d %d %d %d %d %d', [r_lo, r_hi, m_lo, m_hi, o_lo, o_hi]));
  writeln(f);
  
  writeln(f, '#AREA');
  writeln(f, Self.name);
  writeln(f, Self.author);
  writeln(f, Self.resetmsg);
  writeln(f, Self.maxage);
  writeln(f, Self.weather.temp_mult, ' ', Self.weather.temp_avg, ' ', Self.flags.value);
  writeln(f);
  writeln(f, '#ROOMS');
  
  iterator := room_list.iterator();
  
  while (iterator.hasNext()) do
    begin
    room := GRoom(iterator.next());

    if (room.area <> Self) then
      continue;

    writeln(f, '#', room.vnum);
    writeln(f, room.name);
    write(f, room.description);
    writeln(f, '~');

    write(f, room.flags.value, ' ', room.minlevel, ' ', room.maxlevel, ' ', room.sector);

    if (room.flags.isBitSet(ROOM_TELEPORT)) then
      writeln(f, ' ', room.televnum, ' ', room.teledelay)
    else
      writeln(f);

    node_ex := room.exits.head;
    while (node_ex <> nil) do
      begin
      ex := GExit(node_ex.element);

      write(f, 'D ', ex.vnum, ' ', ex.direction, ' ', ex.flags, ' ', ex.key);

      if (ex.keywords <> nil) and (length(ex.keywords^) > 0) then
        writeln(f, ' ', ex.keywords^)
      else
        writeln(f);

      node_ex := node_ex.next;
      end;

    node_ex := room.extra.head;
    while (node_ex <> nil) do
      begin
      extra := GExtraDescription(node_ex.element);

      writeln(f, 'E ', extra.keywords);
      write(f, extra.description);
      writeln(f, '~');

      node_ex := node_ex.next;
      end;

    writeln(f, 'S');
    end;
    
  iterator.Free();

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

  iterator := npc_list.iterator();
  while (iterator.hasNext()) do
    begin
    npcindex := GNPCIndex(iterator.next());

    if (npcindex.area <> Self) then
      continue;

    writeln(f, '#', npcindex.vnum);

    writeln(f, npcindex.name^);
    writeln(f, npcindex.short^);
    writeln(f, npcindex.long^);

    write(f, npcindex.level, ' ', npcindex.sex);

    if (npcindex.clan <> nil) then
      writeln(f, '''' + npcindex.clan.name + '''')
    else
      writeln(f);

    writeln(f, npcindex.natural_ac, ' ', npcindex.act_flags, ' ', npcindex.gold, ' ', npcindex.height, ' ', npcindex.weight);

{    node_ex := npcindex.programs.head;
    while (node_ex <> nil) do
      begin
      prog := node_ex.element;

      case prog.prog_type of
             MPROG_ACT : write(f, '> on_act ');
           MPROG_GREET : write(f, '> on_greet ');
        MPROG_ALLGREET : write(f, '> on_allgreet ');
           MPROG_ENTER : write(f, '> on_enter ');
           MPROG_DEATH : write(f, '> on_death ');
           MPROG_BRIBE : write(f, '> on_bribe ');
           MPROG_FIGHT : write(f, '> on_fight ');
            MPROG_RAND : write(f, '> on_rand ');
           MPROG_BLOCK : write(f, '> on_block ');
           MPROG_RESET : write(f, '> on_reset ');
           MPROG_GIVE  : write(f, '> on_give ');
      end;

      writeln(f, prog.args);

      write(f, prog.code);
      writeln(f,'~');

      node_ex := node_ex.next;
      end; }

    node_ex := npcindex.skills_learned.head;;
    while (node_ex <> nil) do
      begin
      g := GLearned(node_ex.element);

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

      node_ex := node_ex.next;
      end;
      
    if (npcindex.progfile <> '') then
    	writeln(f, '>', right(npcindex.progfile, PathDelimiter));
    end;
  iterator.Free();

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

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

    if (obj.area <> Self) then
      continue;

    writeln(f, '#',obj.vnum);
    writeln(f, obj.name);
    writeln(f, obj.short);
    writeln(f, obj.long);
    writeln(f, obj.item_type,' ',obj.wear_location1,' ',obj.wear_location2);
    writeln(f, obj.value[1],' ',obj.value[2],' ',obj.value[3],' ',obj.value[4]);
    writeln(f, obj.weight,' ',obj.flags,' ',obj.cost);
    end;
  iterator.Free();

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

  iterator := Self.resets.iterator();
  while (iterator.hasNext()) do
    begin
    reset := GReset(iterator.next());

    writeln(f, reset.reset_type, ' ', reset.arg1, ' ', reset.arg2, ' ', reset.arg3);
    end;
  iterator.Free();

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

  iterator := shops.iterator();
  while (iterator.hasNext()) do
    begin
    shop := GShop(iterator.next());

    if (shop.area <> Self) then
      continue;

    writeln(f, shop.keeper);
    writeln(f, shop.item_buy[1],' ',shop.item_buy[2],' ',
               shop.item_buy[3],' ',shop.item_buy[4],' ',shop.item_buy[5]);
    writeln(f, shop.open_hour,' ',shop.close_hour);
    writeln(f, '~');
    end;
  iterator.Free();

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

  closefile(f);
end;

{jago - utility func, move to area.pas}
function instanceNPC(npcindex : GNPCIndex) : pointer;
var
  npc : GNPC;
begin
  // note : this func doesnt check
  // npcindex.count + 1 < reset.max
  // this is so imms can mload more npcs than the reset maximum

  // this func does not place the npc in a room, the calling func is
  // responsible for that

  if (npcindex = nil) then
    begin
    bugreport('instanceNPC', 'area.pas', 'npc_index null');
    Result := nil;
    exit;
    end;

  npc := GNPC.Create();
  npc.context := GContext.Create(npc);
  npc.context.load(npcindex.prog);

  with npc do
    begin
    str := npcindex.str;
    con := npcindex.con;
    dex := npcindex.dex;
    int := npcindex.int;
    wis := npcindex.wis;
    hp := npcindex.hp;
    max_hp := npcindex.hp;
    mv := npcindex.mv;
    max_mv := npcindex.mv;
    mana := npcindex.mana;
    max_mana := npcindex.mana;
    natural_ac := npcindex.natural_ac;
    ac_mod := 0;
    hitroll := npcindex.hitroll;

    damnumdie := npcindex.damnumdie;
    damsizedie := npcindex.damsizedie;
    apb := npcindex.apb;
    skills_learned := npcindex.skills_learned;
    clan := npcindex.clan;
    npc.room := nil;
    position := POS_STANDING;
    state := STATE_IDLE;
    npc.npc_index := npcindex;

	  name := npcindex.name^;
    short := npcindex.short^;
    long := npcindex.long^;

    sex := npcindex.sex;
    race := npcindex.race;
    alignment := npcindex.alignment;
    level := npcindex.level;
    weight := npcindex.weight;
    height := npcindex.height;
    act_flags := npcindex.act_flags;
    end;

  inc(npcindex.count);
  npc.node_world := char_list.insertLast(npc);

  npc.calcAC;

  Result := npc;
end;

procedure GArea.reset();
var 
	reset : GReset;
	npc, lastmob : GNPC;
	vict : GCharacter;
	obj, lastobj, tempobj : GObject;
	npcindex : GNPCIndex;
	room : GRoom;
	pexit : GExit;
	conn : GPlayerConnection;
	iterator, in_iterator : GIterator;
	buf : string;
begin
  lastobj := nil;
  lastmob := nil;

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

    if (conn.isPlaying()) and (conn.ch.room.area = Self) then
      begin
      buf := conn.ch.ansiColor(AT_REPORT) + resetmsg + #13#10;
      conn.ch.sendBuffer(buf);
      end;
    end;
    
  iterator.Free();

  iterator := resets.iterator();
  
  while (iterator.hasNext()) do
    begin
    reset := GReset(iterator.next());

    case reset.reset_type of
      'M':begin
          npcindex := findNPCIndex(reset.arg1);

          if (npcindex = nil) then
            bugreport('GArea.reset (M) area: ' + name, 'area.pas', 'npc #' + IntToStr(reset.arg1) + ' null')
          else
            begin
            lastmob := nil;

            if (npcindex.count < reset.arg3) then
              begin
              npc := instanceNPC(npcindex);
              npc.room := findRoom(reset.arg2);

              if (npc.room = nil) then
                begin
                bugreport('GArea.reset (M) area: ' + name, 'area.pas', 'room #' + IntToStr(reset.arg2) + ' null');

                npc.extract(true);
                end
              else
                begin
                npc.calcAC();

                npc.toRoom(npc.room);
                lastmob := npc;
                inc(mobs_loaded);

								npc.context.runSymbol('onReset', [integer(npc)]);
                end;
              end;
            end;
          end;
      'E':begin
          npc:=nil;

          if (reset.arg3<>0) then
            begin
            in_iterator := char_list.iterator();

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

              if (vict.IS_NPC) and (GNPC(vict).npc_index.vnum = reset.arg3) then
                begin
                npc := GNPC(vict);
                break;
                end;
              end;
              
            in_iterator.Free();

            if (npc = nil) then
              begin
              bugreport('GArea.reset (E) area: ' + name, 'area.pas', '(' + IntToStr(reset.arg1) + ') npc #' + IntToStr(reset.arg3) + ' null');
              continue;
              end;
            end
          else
            npc := lastmob;

          if lastmob=nil then
            continue;

          if (npc = nil) then
            bugreport('GArea.reset (E) area: ' + name, 'area.pas', '(' + IntToStr(reset.arg1) + ') npc #' + IntToStr(reset.arg3) + ' null')
          else
          if (number_percent <= reset.arg2) then
            begin
            tempobj := GObject(objectIndices[reset.arg1]);
            
            if (tempobj <> nil) then
              begin
              obj := tempobj.clone();

	            obj.toChar(npc);
	            npc.equip(obj, true);

	            lastobj := obj;
	            end
	          else
              bugreport('GArea.reset (E) area: ' + name, 'area.pas', 'obj #' + IntToStr(reset.arg1) + ' null');
            end;
          end;
      'G':begin
          npc := nil;

          if (reset.arg3 <> 0) then
            begin
            in_iterator := char_list.iterator();

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

              if (vict.IS_NPC) and (GNPC(vict).npc_index.vnum = reset.arg3) then
                begin
                npc := GNPC(vict);
                break;
                end;
              end;
              
            in_iterator.Free();

            if (npc = nil) then
              begin
              bugreport('GArea.reset (G) area: ' + name, 'area.pas', '(' + IntToStr(reset.arg1) + ') npc #' + IntToStr(reset.arg3) + ' null');
              continue;
              end;
            end
          else
            npc := lastmob;

          if lastmob=nil then
            continue;

					tempobj := GObject(objectIndices[reset.arg1]);
					
					if (tempobj <> nil) then
					  begin
            obj := tempobj.clone();
            obj.toChar(npc);

            lastobj := obj;
					  end
					else
            bugreport('GArea.reset (G) area: ' + name, 'area.pas', 'obj #' + IntToStr(reset.arg1) + ' null');
          end;
      'O':begin
          tempobj := GObject(objectIndices[reset.arg1]);
          
          if (tempobj = nil) then
            bugreport('GArea.reset (O) area: ' + name, 'area.pas', 'obj #' + IntToStr(reset.arg1) + ' null')
          else
          if (tempobj.area.nplayer = 0) and (reset.arg3 > tempobj.child_count) then
            begin
            obj := tempobj.clone();
            obj.toRoom(findRoom(reset.arg2));

            lastobj := obj;
            end;
          end;
      'I':begin
          tempobj := GObject(objectIndices[reset.arg1]);

          if (lastobj = nil) then
            continue;

          if (tempobj = nil) then
            bugreport('GArea.reset (I) area: ' + name, 'area.pas', 'obj #' + IntToStr(reset.arg1) + ' null')
          else
          if (tempobj.area.nplayer = 0) and (reset.arg3 > tempobj.child_count) then
            begin
            obj := tempobj.clone();
            obj.toObject(lastobj);
            end;
          end;
      'D':begin
          room := findRoom(reset.arg1);

          if (room = nil) then
            begin
            bugreport('GArea.reset (D) area: ' + name, 'area.pas', 'room #' + IntToStr(reset.arg1) + ' null');
            continue;
            end;

          pexit := room.findExit(reset.arg2);

          if (pexit = nil) then
            begin
            bugreport('GArea.reset (D) area: ' + name, 'area.pas', 'direction ' + IntToStr(reset.arg2) + ' has no exit in room ' + IntToStr(reset.arg1));
            continue;
            end;

          // Added reverse exits - Nemesis
          case reset.arg3 of
          // open door
            0 : begin
                REMOVE_BIT(pexit.flags, EX_LOCKED);
                REMOVE_BIT(pexit.flags, EX_CLOSED);

                // reverse exit
                room := findRoom(pexit.vnum);
                pexit := room.findExit(dir_inv[reset.arg2]);

                REMOVE_BIT(pexit.flags, EX_LOCKED);
                REMOVE_BIT(pexit.flags, EX_CLOSED);
                end;
          // closed door
            1 : begin
                REMOVE_BIT(pexit.flags, EX_LOCKED);
                SET_BIT(pexit.flags, EX_CLOSED);

                // reverse exit
                room := findRoom(pexit.vnum);
                pexit := room.findExit(dir_inv[reset.arg2]);

                REMOVE_BIT(pexit.flags, EX_LOCKED);
                SET_BIT(pexit.flags, EX_CLOSED);
                end;
          // closed secret door
            2 : begin
                REMOVE_BIT(pexit.flags, EX_LOCKED);
                SET_BIT(pexit.flags, EX_CLOSED);
                SET_BIT(pexit.flags, EX_SECRET);

                // reverse exit
                room := findRoom(pexit.vnum);
                pexit := room.findExit(dir_inv[reset.arg2]);

                REMOVE_BIT(pexit.flags, EX_LOCKED);
                SET_BIT(pexit.flags, EX_CLOSED);
                SET_BIT(pexit.flags, EX_SECRET);
                end;
          // locked door
            3 : begin
                SET_BIT(pexit.flags, EX_LOCKED);
                SET_BIT(pexit.flags, EX_CLOSED);

                // reverse exit
                room := findRoom(pexit.vnum);
                pexit := room.findExit(dir_inv[reset.arg2]);

                SET_BIT(pexit.flags, EX_LOCKED);
                SET_BIT(pexit.flags, EX_CLOSED);
                end;
          // locked secret door
            4 : begin
                SET_BIT(pexit.flags, EX_LOCKED);
                SET_BIT(pexit.flags, EX_CLOSED);
                SET_BIT(pexit.flags, EX_SECRET);

                // reverse exit
                room := findRoom(pexit.vnum);
                pexit := room.findExit(dir_inv[reset.arg2]);

                SET_BIT(pexit.flags, EX_LOCKED);
                SET_BIT(pexit.flags, EX_CLOSED);
                SET_BIT(pexit.flags, EX_SECRET);
                end;
          end;
          end;
    end;

    end;

  _age := 0;
end;

procedure GArea.update();
var 
	buf : string;
	diff : integer;
	conn : GPlayerConnection;
	iterator : GIterator;
begin
  inc(_age);

  if (_age >= _maxage) then
    begin
    writeConsole('Resetting ' + fname + '...');

    reset();
    end;

  { weather routine, adapted from Smaug code - Grimlord }
  { put into local mode, different weather for different areas }

  buf := '';

  if (time_info.month >= 9) and (time_info.month <= 16) then
    begin
    if (weather.mmhg > 985) then
      diff := -2
    else
      diff := 2;
    end
  else
    begin
    if (weather.mmhg > 1015) then
      diff := -2
    else
      diff := 2;
    end;

  inc(weather.change, diff*rolldice(1,4)+rolldice(2,6)-rolldice(2,6));
  weather.change := URange(-12, weather.change, 12);

  weather.mmhg := URANGE(960,weather.mmhg + weather.change,1060);
  weather.temp := round(sin((time_info.hour-12)*PI/12)*weather.temp_mult)+weather.temp_avg+diff;

  case weather.sky of
    SKY_CLOUDLESS:begin
                  if (weather.mmhg < 1000) or ((weather.mmhg < 1020) and (random(4) < 2)) then
                    begin
                    buf := 'The sky is getting cloudy.';
                    weather.sky := SKY_CLOUDY;
                    end;
                  end;
       SKY_CLOUDY:begin
                  if (weather.mmhg < 980) or ((weather.mmhg < 1000) and (random(4) < 2)) then
                    begin
                    buf := 'It starts to rain.';
                    weather.sky := SKY_RAINING;
                    end
                  else
                  if (weather.mmhg > 1030) and (random(4) < 2) then
                    begin
                    buf := 'The clouds disappear.';
                    weather.sky := SKY_CLOUDLESS;
                    end;
                  end;
      SKY_RAINING:begin
                  if (weather.mmhg < 970) then
                   case random(4) of
                     1:begin
                       buf := 'Lightning flashes in the sky.';
                       weather.sky := SKY_LIGHTNING;
                       end;
                     2:begin
                       buf := 'Fierce winds start blowing as a storm approaches.';
                       weather.sky := SKY_STORMING;
                       end;
                   end;
                  if (weather.mmhg > 1030) or ((weather.mmhg > 1010) and (random(4) < 2)) then
                    begin
                    buf := 'The rain stopped.';
                    weather.sky := SKY_CLOUDY;
                    end
                  else
                  if (weather.temp < 0) then
                    begin
                    buf := 'Snowflakes fall on your head.';
                    weather.sky := SKY_SNOWING;
                    end;
                  end;
      SKY_SNOWING:begin
                  if (weather.mmhg < 970) then
                   case random(4) of
                     1:begin
                       buf := 'The sky lights up as lightning protrudes the snow.';
                       weather.sky := SKY_LIGHTNING;
                       end;
                     2:begin
                       buf := 'A blizzard blows snow in your face.';
                       weather.sky := SKY_STORMING;
                       end;
                   end;
                  if (weather.mmhg > 1030) or ((weather.mmhg > 1010) and (random(4) < 2)) then
                    begin
                    buf := 'The snowflakes stop falling down';
                    weather.sky := SKY_CLOUDY;
                    end
                  else
                  if (weather.temp > 1) then
                    begin
                    buf := 'The snow turns into wet rain.';
                    weather.sky := SKY_RAINING;
                    end;
                  end;
    SKY_LIGHTNING:begin
                  if (weather.mmhg > 1010) or ((weather.mmhg > 990) and (random(4) < 2)) then
                    begin
                    buf := 'The lightning has stopped.';
                    weather.sky := SKY_RAINING;
                    end;
                  end;
     SKY_STORMING:begin
                  if (weather.mmhg > 1010) or ((weather.mmhg > 990) and (random(4) < 2)) then
                    begin
                    buf := 'The winds subside.';
                    weather.sky := SKY_CLOUDY;
                    end;
                  end;
   else
     begin
     bugreport('GArea.update', 'update.pas', 'bad sky identifier');
     weather.sky := SKY_CLOUDLESS;
     end;
  end;

  if (weather.temp<1) then
    begin
    if (length(buf) > 0) then
      buf := buf + #13#10;

    buf := buf + 'Brrr... it is very cold...';
    end
  else
  if (weather.temp>28) and (weather.temp<35) then
    begin
    if (length(buf) > 0) then
      buf := buf + #13#10;

    buf := buf + 'It is quite hot!';
    end
  else
  if (weather.temp>=35) then
    begin
    if (length(buf) > 0) then
      buf := buf + #13#10;

    buf := buf + 'It is VERY hot!';
    end;

  iterator := connection_list.iterator();

  while (iterator.hasNext()) do
    begin
    conn := GPlayerConnection(iterator.next());
    
    if (conn.isPlaying()) and (conn.ch.room.area = Self) and (conn.ch.IS_OUTSIDE) then
      begin
      if (length(buf) > 0) and (conn.ch.IS_AWAKE) then
        act(AT_REPORT,buf,false,conn.ch,nil,nil,TO_CHAR);

      case weather.sky of
(*        SKY_RAINING:if not IS_SET(conn.ch.aff_flags,AFF_COLD) then
                     if number_percent<=5 then
                      if not saving_throw(0,conn.ch.point.save_cold,conn.ch) then
                       begin
                       act(AT_REPORT,'You begin to sneeze... WWWWAAAAATTTCHA!',false,conn.ch,nil,nil,TO_CHAR);
                       act(AT_REPORT,'WWWWWAAAAAAAAAAAAAAAAAAATCHAAAAAAA!!!!! $n sneezes loudly.',false,conn.ch,nil,nil,TO_ROOM);
                       add_affect(conn.ch,skill_table[gsn_cold].affect);
                       end; *)
      SKY_LIGHTNING:if number_percent<=5 then
                      begin
                      act(AT_REPORT,'ZAP! A lightning bolt hits you!',false,conn.ch,nil,nil,TO_CHAR);
                      act(AT_REPORT,'$n''s hairs are scorched as a lightning bolt hits $m.',false,conn.ch,nil,nil,TO_ROOM);
                      damage(conn.ch,conn.ch,25,TYPE_SILENT);
                      end;
      end;
      end;
    end;
  
  iterator.Free();
end;

{ Xenon 28/Apr/2001: moved createRoom() from cmd_build.inc to area.pas }
function createRoom(vnum : integer; area : GArea) : GRoom;
var
   room : GRoom;
begin
  room := GRoom.Create(vnum, area);
  room.name := 'Floating in a void';
  room.description := 'Merely wisps of gas and steam, this room has not yet been clearly defined.'#13#10;

  room_list.put(vnum, room);

  Result := room;
end;

// Find area by filename
function findArea(const fname : string) : GArea;
var
	iterator : GIterator;
  area : GArea;
begin
  Result := nil;

  iterator := area_list.iterator();

  while (iterator.hasNext()) do
    begin
    area := GArea(iterator.Next());

    if (area.fname = fname) then
      begin
      Result := area;
      break;
      end;
    end;
  
  iterator.Free();
end;

constructor GCoords.Create();
begin
  inherited Create();
  
  x := 0;
  y := 0;
  z := 0;
end;

constructor GCoords.Create(coords : GCoords);
begin
  inherited Create();
  
  copyFrom(coords);
end;

function GCoords.toString() : string;
begin
  Result := '(' + IntToStr(x) + ',' + IntToStr(y) + ',' + IntToStr(z) + ')';
end;

procedure GCoords.copyTo(coords : GCoords);
begin
  coords.x := x;
  coords.y := y;
  coords.z := z;
end;

procedure GCoords.copyFrom(coords : GCoords);
begin
  x := coords.x;
  y := coords.y;
  z := coords.z;
end;

// GRoom
constructor GRoom.Create(vn : integer; ar : GArea);
begin
  inherited Create();

  _vnum := vn;
  _sector := 1;
  _light := 0;
  
  area := ar;
  areacoords := nil;
  worldcoords := nil;

  flags := GBitVector.Create(0);
  extra := GDLinkedList.Create();
  exits := GDLinkedList.Create();
  chars := GDLinkedList.Create();
  objects := GDLinkedList.Create();
  tracks := GDLinkedList.Create();
end;

destructor GRoom.Destroy;
begin
  unhash_string(_name);

  extra.clear();
  exits.clear();
  chars.clear();
  objects.clear();
  tracks.clear();

  extra.Free();
  exits.Free();
  chars.Free();
  objects.Free();
  tracks.Free();

  inherited Destroy();
end;

procedure GRoom.setName(const name : string);
begin 
  if (_name <> nil) then
    unhash_string(_name);
    
  _name := hash_string(name);
end;

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

// Room is dark
function GRoom.IS_DARK : boolean;
begin
  if (light > 0) then
    begin
    Result := false;
    exit;
    end;

  if (sector = SECT_INSIDE) or (sector = SECT_CITY) then
    begin
    Result := false;
    exit;
    end;

  if (flags.isBitSet(ROOM_DARK)) then
    begin
    Result := true;
    exit;
    end;

  if (time_info.sunlight = SUN_SET) or (time_info.sunlight = SUN_DARK) then
    begin
    Result := true;
    exit;
    end;

  Result := false;
end;

// Find char in room by name
function GRoom.findChar(c : pointer; name : string) : pointer;
var
	iterator : GIterator;
	num, cnt : integer;
	ch, vict : GCharacter;
begin
  Result := nil;

	if (name = '') then
		exit;

  if (name = 'SELF') then
    begin
    Result := c;
    exit;
    end;

  ch := c;

  num := findNumber(name);

  name := uppercase(name);
  cnt := 0;

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

    if (((name = 'GOOD') and (not vict.IS_NPC) and (vict.IS_GOOD)) or
      ((name = 'EVIL') and (not vict.IS_NPC) and (vict.IS_EVIL)) or
      isNameStart(vict.name, name) or isNameStart(vict.short, name) or
      ((not vict.IS_NPC) and (not ch.IS_SAME_ALIGN(vict)) and
      (isNameAny(vict.race.name, name)))) and (ch.CAN_SEE(vict)) then
      begin
      inc(cnt);

      if (cnt = num) then
        begin
        Result := vict;
        break;
        end;
      end;
    end;

  iterator.Free();
  
  if (Result = nil) then
  	begin
		iterator := chars.iterator();

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

			if (((name = 'GOOD') and (not vict.IS_NPC) and (vict.IS_GOOD)) or
				((name = 'EVIL') and (not vict.IS_NPC) and (vict.IS_EVIL)) or
				isNameAny(vict.name, name) or isNameAny(vict.short, name) or
				((not vict.IS_NPC) and (not ch.IS_SAME_ALIGN(vict)) and
				(isNameAny(vict.race.name, name)))) and (ch.CAN_SEE(vict)) then
				begin
				inc(cnt);

				if (cnt = num) then
					begin
					Result := vict;
					break;
					end;
				end;
			end;

		iterator.Free();
		end;
end;

// Find random char in room
function GRoom.findRandomChar() : pointer;
var
	a, num : integer;
  node : GListNode;
begin
  Result := nil;
  
  if (chars.size() = 0) then
    exit;
  
  num := random(chars.size());

  node := chars.head;
  for a := 0 to num do
    node := node.next;

  if (node <> nil) then
    Result := node.element;
end;

// Find random good aligned char in room
function GRoom.findRandomGood() : pointer;
var 
	a, cnt, num : integer;
	vict : GCharacter;
	iterator : GIterator;
begin
  Result := nil;

  cnt := 0;
  iterator := chars.iterator();

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

    if (vict.IS_GOOD) then
      inc(cnt);
    end;

  iterator.Free();

  num := random(cnt);
  a := 0;

  iterator := chars.iterator();

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

    if (vict.IS_GOOD) and (a = num) then
      begin
      Result := vict;
      break;
      end;
    end;

  iterator.Free();
end;

// Find random evil aligned char in room
function GRoom.findRandomEvil() : pointer;
var 
	a, cnt, num : integer;
	vict : GCharacter;
	iterator : GIterator;
begin
  Result := nil;

  cnt := 0;
  iterator := chars.iterator();

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

    if (vict.IS_EVIL) then
      inc(cnt);
    end;

  iterator.Free();

  num := random(cnt);
  a := 0;

  iterator := chars.iterator();

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

    if (vict.IS_EVIL) and (a = num) then
      begin
      Result := vict;
      break;
      end;
    end;

  iterator.Free();
end;

// Find object by name in room
function GRoom.findObject(name : string) : pointer;
var
	iterator : GIterator;
	obj : GObject;
	num, cnt : integer;
begin
  Result := nil;
  
  if (name = '') then
  	exit;

  iterator := objects.iterator();
  num := findNumber(name);
  cnt := 0;

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

    if isObjectName(obj.name, name) or isObjectName(obj.short, name) or isObjectName(obj.long, name) then
      begin
      inc(cnt, obj.count);

      if (cnt >= num) then
        begin
        Result := obj;
        break;
        end;
      end;
    end;
    
	iterator.Free();
end;

function GRoom.findDescription(const keyword : string) : GExtraDescription;
var
	iterator : GIterator;
	s_extra : GExtraDescription;
	s, p : integer;
	sub, key : string;
begin
  Result := nil;
  
  if (keyword = '') then
  	exit;
  	
  p := high(integer);

  iterator := extra.iterator();
  while (iterator.hasNext()) do
    begin
    s_extra := GExtraDescription(iterator.next());
    key := s_extra.keywords;

    while (length(key) > 0) do
      begin
      key := one_argument(key, sub);
      
      s := pos(keyword, sub);
      if (s > 0) and (s < p) then
        begin
        p := s;
        Result := s_extra;
        end;
      end;
    end;

	iterator.Free();
end;

{ Xenon 7/6/2001: added isConnectedTo() because I needed it for do_map() :-) }
function GRoom.isConnectedTo(dir : integer) : GRoom;
var
	iterator : GIterator;
	pexit : GExit;
begin
	Result := nil;

  iterator := exits.iterator();
  
  while (iterator.hasNext()) do
    begin
    pexit := GExit(iterator.next());

    if (pexit.direction = dir) then
    	begin
      Result := pexit.to_room;
      break;
    	end;
    end;
    
	iterator.Free();
end;

// Find exit by direction in room
function GRoom.findExit(dir : integer) : GExit;
var
	iterator : GIterator;
	pexit : GExit;
begin
  Result := nil;

  iterator := exits.iterator();
  
  while (iterator.hasNext()) do
    begin
    pexit := GExit(iterator.next());

    if (pexit.direction = dir) then
      begin
      Result := pexit;
      break;
      end;
    end;
    
	iterator.Free();
end;

// Find exit by exit keyword
function GRoom.findExitKeyword(s : string) : GExit;
var
	iterator : GIterator;
	pexit : GExit;
begin
  Result := nil;
  
  if (s = '') then
  	exit;
  	
  s := uppercase(s);

  iterator := exits.iterator();
  
  while (iterator.hasNext()) do
    begin
    pexit := GExit(iterator.next());

    if (Assigned(pexit.keywords)) and (pos(s, uppercase(pexit.keywords^)) <> 0) then
      begin
      Result := pexit;
      break;
      end;
    end;
    
	iterator.Free();
end;


// GObject
constructor GObject.Create();
begin
  inherited Create;
  
  _name := nil;
  _short := nil;
  _long := nil;

  worn := '';
  wear_location1 := '';
  wear_location2 := '';
  contents := GDLinkedList.Create();
  affects := GDLinkedList.Create();
  child_count := 0;
  count := 1;
end;

destructor GObject.Destroy();
var 
  obj_in : GObject;
begin
  while (contents.tail <> nil) do
    begin
    obj_in := GObject(contents.tail.element);

    obj_in.Free();
    end;

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

  if (carried_by <> nil) then
    fromChar();

  if (in_obj <> nil) then
    fromObject();

  obj_in := GObject(objectIndices[vnum]);
  
  if (obj_in <> nil) then
    dec(obj_in.child_count);

  if (_name <> nil) then
    unhash_string(_name);
    
  if (_short <> nil) then
    unhash_string(_short);

  if (_long <> nil) then
    unhash_string(_long);

	affects.Free();
  contents.Free();
  
  inherited Destroy();
end;

// Object to room
procedure GObject.toRoom(to_room : GRoom);
var
	iterator : GIterator;
	otmp : GObject;
begin
  if (to_room = nil) then
    begin
    bugreport('GObject.toRoom', 'area.pas', 'room null');
    exit;
    end;

  iterator := to_room.objects.iterator();

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

    if (otmp.group(Self)) then
    	begin
		  iterator.Free();
      exit;
      end;
    end;

	iterator.Free();
	
  node_room := to_room.objects.insertFirst(Self);

  room := to_room;
  in_obj := nil;
  carried_by := nil;
end;

// Object from room
procedure Gobject.fromRoom();
begin
  if (room = nil) then
    begin
    bugreport('obj_from_room', 'area.pas', 'room null');
    exit;
    end;

  room.objects.remove(node_room);
  node_room := nil;
  room := nil;
end;

// Object to char
procedure GObject.toChar(c : pointer);
var 
{	grouped : boolean;
	node : GListNode;
	otmp : GObject; }
	ch : GCharacter;
	oweight : integer;
begin
  oweight := getWeight();
  ch := GCharacter(c);
  
{  
	
	*** TODO!!
	
	grouped := false;

  node := ch.objects.head;

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

    if (otmp.group(Self)) then
      begin
      grouped := true;
      break;
      end;

    node := node.next;
    end; }

  if (worn <> '') then
    ch.equipment[worn] := Self
  else
    node_carry := ch.inventory.insertFirst(Self);
  
  carried_by := c;

  inc(ch.carried_weight, oweight);
end;

// Object from char
procedure GObject.fromChar();
begin
  if (worn <> '') then
    GCharacter(carried_by).equipment.remove(worn)
  else
    GCharacter(carried_by).inventory.remove(node_carry);
    
  dec(GCharacter(carried_by).carried_weight, getWeight);

  worn := '';
  node_carry := nil;
  carried_by := nil;
end;

// Object to object
procedure GObject.toObject(obj : GObject);
var 
	iterator : GIterator;
	otmp : GObject;
begin
  iterator := obj.contents.iterator();

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

    if (otmp.group(Self)) then
    	begin
		  iterator.Free();
      exit;
      end;
    end;
    
  iterator.Free();

  node_in := obj.contents.insertLast(Self);
  in_obj := obj;
end;

// Object from container
procedure GObject.fromObject();
begin
  in_obj.contents.remove(node_in);
  node_in := nil;
  in_obj := nil;
end;

// Get object weight
function GObject.getWeight() : integer;
var 
	we : integer;
	iterator : GIterator;
	obj : GObject;
begin
  we := count * weight;

  iterator := contents.iterator();

  while (iterator.hasNext()) do
    begin
    obj := GObject(iterator.next());
    inc(we, obj.getWeight);
    end;
    
	iterator.Free();

  Result := we;
end;

procedure GObject.setName(const name : string);
begin
  if (_name <> nil) then
    unhash_string(_name);

  _name := hash_string(name);
end;

procedure GObject.setShortName(const name : string);
begin
  if (_short <> nil) then
    unhash_string(_short);

  _short := hash_string(name);
end;

procedure GObject.setLongName(const name : string);
begin
  if (_long <> nil) then
    unhash_string(_long);

  _long := hash_string(name);
end;

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

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

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


// GExit
constructor GExit.Create();
begin
  inherited Create();
// Make sure variables are at least initialised to a value
  vnum := -1;
  direction := 0;
  to_room := nil;
  keywords := nil;
  flags := 0;
  key := 0;
end;

// misc
{Jago 5/Jan/01 : func required for do_goto and do_transfer
		- should probably be placed elsewhere }
function findLocation(ch : pointer; const param : string) : GRoom;
var
  room : GRoom;
  searchVNum : integer;
  victim : GCharacter;
begin
  result := nil;
  
  if (param = '') then	
  	exit;

  searchVNum := StrToIntDef(param, -1);

  if (searchVnum > -1) then
    begin
    room := findRoom(searchVNum);
    Result := room;
    exit;
    end
  else
    begin
    victim := findCharWorld(ch, param);

    if victim <> nil then
      begin
      Result := victim.room;
      exit;
      end;
    end;

 {left out obj's for today}
 (*    if ( ( obj = get_obj_world( ch, arg ) ) != NULL )
	return obj->in_room;
*)
end;

// Find room by vnum
function findRoom(vnum : integer) : GRoom;
begin
  Result := GRoom(room_list.get(vnum));
end;

// Find npcindex by vnum
function findNPCIndex(vnum : integer) : GNPCIndex;
var
	iterator : GIterator;
	npc : GNPCIndex;
begin
  Result := nil;

  iterator := npc_list.iterator();

  while (iterator.hasNext()) do
    begin
    npc := GNPCIndex(iterator.next());

    if (npc.vnum = vnum) then
      begin
      Result := npc;
      break;
      end;
    end;
    
	iterator.Free();
end;

// Add a corpse
procedure addCorpse(c : pointer);
var 
  obj, obj_in : GObject;
  iterator : GIterator;
  ch : GCharacter;
begin
  ch := c;

  obj_in := GObject(objectIndices[OBJ_VNUM_CORPSE]);
  
  if (obj_in = nil) then
  	begin
  	bugreport('area.pas', 'addCorpse', 'index for OBJ_VNUM_CORPSE (' + IntToStr(OBJ_VNUM_CORPSE) + ') not found');
    exit;
    end;
    
  obj := obj_in.clone();

  with obj do
    begin
    name := 'a corpse';
    short := '$4the corpse of ' + ch.name + '$7';
    long := '$4The corpse of ' + ch.name + ' is lying here$7';

    if (not ch.IS_NPC) then
      SET_BIT(flags, OBJ_NOSAC);

    SET_BIT(flags, OBJ_NOPICKUP);

    // player corpses will remain longer than mobiles to give players more
    // opportunity to retreive their items.

    if (ch.IS_NPC) then
      obj.timer := 5
    else
      obj.timer := 20;
    end;

  { when ch dies in bg, we don't want to have him lose all his items! - Grimlord }
  if not (not ch.IS_NPC and (GPlayer(ch).bg_status = BG_PARTICIPATE)) then
    begin
    // Inventory put into corpse as well, but not for shopkeepers of course :)
    if (not ch.IS_SHOPKEEPER) then
      begin
      iterator := ch.inventory.iterator();
      
      while (iterator.hasNext()) do
        begin
        obj_in := GObject(iterator.next());

        if (not IS_SET(obj_in.flags, OBJ_LOYAL)) and (not ((obj_in.worn <> '') and (IS_SET(obj_in.flags, OBJ_NOREMOVE)))) then
          begin
          obj_in.fromChar();
          obj_in.toObject(obj);
          end;
        end;
        
      iterator.Free();

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

        if (not IS_SET(obj_in.flags, OBJ_LOYAL)) and (not ((obj_in.worn <> '') and (IS_SET(obj_in.flags, OBJ_NOREMOVE)))) then
          begin
          obj_in.fromChar();
          obj_in.toObject(obj);
          end;
        end;
        
      iterator.Free();
      end;
    end;
    
  if (ch.gold > 0) then
    begin
    obj_in := GObject.Create();
    
    with obj_in do
      begin
      if (ch.gold = 1) then
        begin
        name := 'one gold coin';
        short := 'one gold coin';
        long := 'one gold coin';
        end
      else
        begin
        name := IntToStr(ch.gold) + ' gold coins';
        short := IntToStr(ch.gold) + ' gold coins';
        long := IntToStr(ch.gold) + ' gold coins';
        end;

      item_type := ITEM_MONEY;

      value[1] := ch.gold;
      
      worn := '';
      wear_location1 := ''; wear_location2 := '';
      weight := 0;
      timer := 0;
      end;

    objectList.add(obj_in);

    obj_in.toObject(obj);
    
    ch.gold := 0;
    end;

  obj.toRoom(ch.room);
end;

function findHeading(s : string) : integer;
var
  a : integer;
begin
  FindHeading := -1;
  
  if (s = '') then
  	exit;
  
  s := lowercase(s);

  for a := DIR_NORTH to DIR_UP do
   if (pos(s, headings[a]) = 1) then
    begin
    Result := a;
    exit;
    end;
end;

function findDirectionShort(startroom, goalroom : GRoom) : string;
var
  i : integer;
begin
  Result := '';
  for i := DIR_NORTH to DIR_UP do
  begin
    if (startroom.isConnectedTo(i) = goalroom) then
    begin
      Result := headings_short[i];
      exit;
    end;
  end;
end;

// Clone object
function GObject.clone() : GObject;
var
  obj : GObject;
  obj_in : GObject;
  iterator : GIterator;
begin
  obj := GObject.Create();

  obj.name := name;
  obj.short := short;
  obj.long := long;
  obj.item_type := item_type;
  obj.wear_location1 := wear_location1;
  obj.wear_location2 := wear_location2;
  obj.flags := flags;
  obj.value[1] := value[1];
  obj.value[2] := value[2];
  obj.value[3] := value[3];
  obj.value[4] := value[4];
  obj.weight := weight;
  obj.cost := cost;
  obj.count := 1;
  obj.vnum := vnum;
  obj.timer := timer;
   
  iterator := affects.iterator();
  
  while (iterator.hasNext()) do
  	begin
  	obj.affects.insertLast(iterator.next());
  	end;
  
  iterator.Free();
  
  obj_in := GObject(objectIndices[vnum]);

  if (obj_in <> nil) then
    inc(obj_in.child_count);

  objectList.add(obj);

  Result := obj;
end;

function GObject.group(obj : GObject) : boolean;
begin
  Result := false;

{  if (obj = nil) or (obj = Self) then
    exit;

  if (Self.name = obj.name) and
   (Self.short = obj.short) and
   (Self.long = obj.long) and
   (Self.item_type = obj.item_type) and
   (Self.wear1 = obj.wear1) and
   (Self.wear2 = obj.wear2) and
   (Self.flags = obj.flags) and
   (Self.cost = obj.cost) and
   (Self.weight = obj.weight) and
   (Self.value[1] = obj.value[1]) and
   (Self.value[2] = obj.value[2]) and
   (Self.value[3] = obj.value[3]) and
   (Self.value[4] = obj.value[4]) and
   (Self.wear_location = obj.wear_location) and
   (Self.contents.getSize() = 0) and (obj.contents.getSize() = 0) then
    begin
    inc(count, obj.count);

    if (obj_index <> nil) then
      inc(obj_index.obj_count, obj.count);

    obj.extract();

    Result := true;
    exit;
    end; }
end;

procedure GObject.split(num : integer);
{ var
   rest : GObject; }
begin
{  if (count <= num) or (num = 0) then
    exit;

  rest := clone();

  if (obj_index <> nil) then
    dec(obj_index.obj_count);

  rest.count := count - num;
  count := num;

  if (carried_by <> nil) then
    begin
    rest.node_carry := GCharacter(carried_by).objects.insertLast(rest);
    rest.carried_by := carried_by;
    rest.room := nil;
    rest.in_obj := nil;
    end
  else
  if (room <> nil) then
    begin
    rest.node_room := room.objects.insertLast(rest);
    rest.carried_by := nil;
    rest.room := room;
    rest.in_obj := nil;
    end
  else
  if (in_obj <> nil) then
    begin
    rest.toObject(in_obj);
    rest.in_obj := in_obj;
    rest.room := nil;
    rest.carried_by := nil;
    end; }
end;

// Seperate a grouped object
procedure GObject.seperate;
begin
  split(1);
end;

{Jago 10/Jan/2001 - utility function }
{ Revised 28/Jan/2001 - Nemesis }
function findObjectWorld(s : string) : GObject;
var 
  obj : GObject;
  iterator : GIterator;
  number, count : integer;
begin
  Result := nil;
  
  if (s = '') then
  	exit;
  	
  number := findNumber(s); // eg 2.sword

  count := 0;

  iterator := objectList.iterator();

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

    if (isName(obj.name,s)) then
      begin
      inc(count);

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

procedure initAreas();
begin
  area_list := GDLinkedList.Create();

  room_list := GHashTable.Create(32768);
  room_list.ownsObjects := false;
  
  teleport_list := GDLinkedList.Create();

  npc_list := GDLinkedList.Create();
  npc_list.ownsObjects := false;
  
  objectList := GDLinkedList.Create();
  objectList.ownsObjects := false;
  
  objectIndices := GHashTable.Create(32768);
  objectIndices.ownsObjects := false;
end;

procedure cleanupAreas();
begin
  area_list.clear();
  area_list.Free();
  
  room_list.clear();
  room_list.Free();

  teleport_list.clear();
  teleport_list.Free();

  npc_list.clear();
  npc_list.Free();

  objectList.clear();
  objectList.Free();

  objectIndices.clear();
  objectIndices.Free();
end;

end.