areaeditor/
/*
** AreaEditor - a program for editing SMAUG and ROM area files.
** Author: Nick Gammon
** http://www.gammon.com.au/
** See Copyright Notice at the end of AreaEditor.h
*/

#include "stdafx.h"
#include "AreaEditor.h"

#include "MainFrm.h"
#include "defaults.h"

#include "AreaEditorDoc.h"
#include "AreaLoadingProblems.h"
#include "AreaEditorView.h"
#include "ProgDlg.h"
       

CString strMessage;
int nErrors;
int iTotalPrograms;

void Comment (LPCTSTR lpszFormat, ...)
{

	ASSERT(AfxIsValidString(lpszFormat, FALSE));

	va_list argList;
	va_start(argList, lpszFormat);
	strMessage += CFormat (lpszFormat, argList);
  strMessage += ENDLINE;
	va_end(argList);

}

void Warning (CMUDitem * item, LPCTSTR lpszFormat, ...)
{

	ASSERT(AfxIsValidString(lpszFormat, FALSE));

	va_list argList;
	va_start(argList, lpszFormat);
  if (item)
    {

    CString strType = item->Type ();
    if (!strType.IsEmpty ())
	    strType.SetAt (0, UPPER(strType[0])); // capitalise first character

    strMessage += "** ";
    strMessage += strType;
    strMessage += ": ";
    strMessage += item->Summary ();
    if (strlen (lpszFormat) > 0)
      strMessage += ": ";
    }

  strMessage += CFormat (lpszFormat, argList);
  strMessage += ENDLINE;
	va_end(argList);

  nErrors++;

}

#ifdef SPELL_CHECKER

int SpellCheckBlock (CMUDitem * item, 
                     CString strText,
                     CStringList & strBadWordsList)
  {
SSCE_S16 sid;
SSCE_S16 blkId;
SSCE_S16 resultMask;
unsigned char * p;
SSCE_CHAR  errWord [SSCE_MAX_WORD_SZ];
SSCE_CHAR  replacement [SSCE_MAX_WORD_SZ];

CString strBadWords;
CString strError;
int iLength = strText.GetLength ();
int iCount = 0;
POSITION pos = strBadWordsList.GetTailPosition ();

  // get session ID
  sid = SSCE_GetSid ();

  if (sid < 0)
    return 0;   // cannot open spell checker

  p = (unsigned char * ) strText.GetBuffer (iLength + 2);

  blkId = SSCE_OpenBlock (sid, p, strText.GetLength (), iLength + 1, 0);

  if (blkId < 0)
    {
    strText.ReleaseBuffer ();
    return 0;   // cannot open block
    }


  do
    {
    // check the next word
    resultMask = SSCE_CheckBlock
      (sid, blkId, errWord, SSCE_MAX_WORD_SZ, replacement, SSCE_MAX_WORD_SZ);

    if (resultMask < 0)
      break;    // oops! bail out

    if (resultMask & SSCE_END_OF_BLOCK_RSLT)
      break;

    if (resultMask != 0)
      {
      if (resultMask & SSCE_DOUBLED_WORD_RSLT)
        {
        strError = errWord;
        strError += " ";
        strError += errWord;   // double it up so they know what we are about
        }
      else
        strError = errWord;

      if (strBadWordsList.Find (strError) == NULL)
        {
        strBadWordsList.AddTail (strError);
        iCount++;
        }   // end of word not in list
      // now get next word
      SSCE_NextBlockWord(sid, blkId);
      }   // end of misspelling

    } while ((resultMask & SSCE_END_OF_BLOCK_RSLT) == 0);

  SSCE_CloseBlock (sid, blkId);
  strText.ReleaseBuffer ();

  // don't report ones which were already in the list
  if (pos)
    strBadWordsList.GetNext (pos);
  else
    pos = strBadWordsList.GetHeadPosition ();

  if (pos)
    {
    while (pos)
      {
      CString strWord = strBadWordsList.GetNext (pos);
      if (strBadWords.IsEmpty ())
        strBadWords = CFormat ("%i spelling error%s: ", 
                                PLURAL (iCount));
      else
        strBadWords += ", ";
      strBadWords += strWord;
      }  // end of building up list

    if (item)
      ::Warning (item, strBadWords);
    }  // end of at least one error


  return iCount;
  }   // end of  SpellCheckBlock

#endif // SPELL_CHECKER


void CAreaEditorDoc::AreaCheck(int & iTotalRooms,
                               int & iTotalMobs,
                               int & iTotalObjects,
                               int & iTotalExits,
                               int & iTotalPrograms) 
{
POSITION mobPos;
POSITION RoomPos;
POSITION ObjectPos;
POSITION pos;


BOOL bWarnVnumsOutOfRange = App.GetProfileInt  (sProfilePreferences, sProfileWarnVnums, 1);

	CWaitCursor wait;

// this scans the area looking for problems

  // the date
  CString strTime;
  CTime theTime;
  theTime = CTime::GetCurrentTime();
  strTime = theTime.Format ("%A, %B %d, %Y, %#I:%M %p");

  ::Comment ("Checking area %s on %s", 
          m_Area ? (LPCTSTR) m_Area->strAreaName : "(untitled)",
          (LPCTSTR) strTime);

  // count all programs

  // count mob programs
  for (mobPos = m_MobList.GetHeadPosition (); mobPos; )
    iTotalPrograms += m_MobList.GetNext (mobPos)->programlist.GetCount ();

  // count object programs
  for (ObjectPos = m_ObjectList.GetHeadPosition (); ObjectPos; )
    iTotalPrograms += m_ObjectList.GetNext (ObjectPos)->programlist.GetCount ();

  // count room programs
  for (RoomPos = m_RoomList.GetHeadPosition (); RoomPos; )
    iTotalPrograms += m_RoomList.GetNext (RoomPos)->programlist.GetCount ();

  if (m_Area)
    {
    ::Comment ("Author: %s", (LPCTSTR) m_Area->strAuthor);
    ::Comment ("Reset message: %s", (LPCTSTR) m_Area->strResetMsg);
    ::Comment ("Levels: Soft: %i to %i, Hard: %i to %i", 
              m_Area->low_soft_range,
              m_Area->hi_soft_range,
              m_Area->low_hard_range,
              m_Area->low_hard_range);
    ::Comment ("Repop frequency: %s", m_Area->reset_frequency > 0 
              ? (LPCTSTR) CFormat ("%i", m_Area->reset_frequency) : "default");
    ::Comment ("Mobile vnums %i to %i", m_Area->mob_low_vnum, m_Area->mob_hi_vnum);
    ::Comment ("Object vnums %i to %i", m_Area->obj_low_vnum, m_Area->obj_hi_vnum);
    ::Comment ("Room vnums %i to %i", m_Area->room_low_vnum, m_Area->room_hi_vnum);
    }

  ::Comment ("");

  // who cares if we can't check programs if we don't have any?
  if (iTotalPrograms)
    {
    if (App.m_CommandList.IsEmpty ())
      ::Comment ("** WARNING - Commands file (commands.dat) not loaded.");
    
    if (App.m_SocialList.IsEmpty ())
      ::Comment ("** WARNING - Socials file (socials.dat) not loaded.");

    if (App.m_SkillList.IsEmpty ())
      ::Comment ("** WARNING - Skills file (skills.dat) not loaded.");

    if (App.m_CommandList.IsEmpty () 
     || App.m_SocialList.IsEmpty ()
     || App.m_SkillList.IsEmpty ())
      {
      ::Comment ("** Cannot check MUD program syntax.");
      ::Comment ("");
      }
    }

  // now look for program cross-references

  // process each mob

  CMobile * mob;
  CMUDObject * object;
  CRoom * room;
  CReset * reset;
  CExit * exit;
  CShop * shop;
  CRepair * repair;

  CMobile * refmob;
  CMUDObject * refobject;
  CRoom * refroom;

  CMUDprogram * program;
  CString strMessage;
  int vnum;
  int iExits;
  int OldnErrors = 0;
  int iPrograms;
  int iKeys = 0;
  int iHighLevel = 0;
  int iLowLevel = INT_MAX;
  int iSpellErrors = 0;


  int * pHPs = NULL;   // array of Hit points at a particular level
  int * pLowHPs = NULL;   // array of lowest Hit points at a particular level
  int * pHighHPs = NULL;   // array of highest Hit points at a particular level
  int * pCount = NULL; // array of counts of mob per level
  int * pAggressiveCount = NULL; // array of counts of mob per level

  long iMilestone = 0;

  long iItems =  m_MobList.GetCount ()
                   + m_ObjectList.GetCount ()
                   + m_RoomList.GetCount ()
                   + m_ResetList.GetCount ()
                   + m_ShopList.GetCount ()
                   + m_RepairList.GetCount ()
                   + m_HelpList.GetCount ();
  
  Frame.CreateProgressBar ("Checking area", iItems);

// check each mob's programs

  ::Comment ("Checking mobs");
  ::Comment ("");
  iPrograms = 0;
  OldnErrors = nErrors;

  
// find highest and lowest mob level

  for (mobPos = m_MobList.GetHeadPosition (); mobPos; )
    {
    mob = m_MobList.GetNext (mobPos);

    if (mob->level >= 0 && mob->level <= 1000)
      {
      if (mob->level > iHighLevel)
        iHighLevel = mob->level;

      if (mob->level < iLowLevel)
        iLowLevel = mob->level;
      }

    }   // end of checking each mob


  if (!m_MobList.IsEmpty ())
    {
    pHPs = new int [iHighLevel + 1];
    pLowHPs = new int [iHighLevel + 1];
    pHighHPs = new int [iHighLevel + 1];
    pCount = new int [iHighLevel + 1];
    pAggressiveCount = new int [iHighLevel + 1];

    for (int i = 0; i <= iHighLevel; i++)
      {
      pHPs [i] = 0;
      pLowHPs [i] = INT_MAX;
      pHighHPs [i] = 0;
      pCount [i] = 0;
      pAggressiveCount [i] = 0;
      }
    }

  iSpellErrors = 0;
  for (mobPos = m_MobList.GetHeadPosition (); mobPos; )
    {
    mob = m_MobList.GetNext (mobPos);

    if (iMilestone++ % 32 == 0)
      Frame.UpdateProgressBar (iMilestone);

    // ===================================================================
    // do various checks against config file

    // check vnum
    if (bWarnVnumsOutOfRange)
      if (mob->vnum < 1 || mob->vnum > iMaxVnum)
        ::Warning (mob, "Mob vnum of %i outside recommended range of 1 to %i",
                 mob->vnum, iMaxVnum);

    // check level
    if (mob->level < 1 || mob->level > iMaxLevel)
      ::Warning (mob, "Mob level of %i outside recommended range of 1 to %i",
               mob->level, iMaxLevel);

    // armour class
    if (mob->ac < iMinAC || mob->ac > iMaxAC)
      ::Warning (mob, "Mob armour class of %i outside recommended range of %i to %i",
               mob->ac, iMinAC, iMaxAC);

    // damnodice
    if (mob->damnodice < 0 || mob->damnodice > iMaxdamnodice)
      ::Warning (mob, "Mob damnodice of %i outside recommended range of 0 to %i",
               mob->damnodice, iMaxdamnodice);

    // damplus
    if (mob->damplus < 0 || mob->damplus > iMaxdamplus)
      ::Warning (mob, "Mob damplus of %i outside recommended range of 0 to %i",
               mob->damplus, iMaxdamplus);

    // damroll
    if (mob->damroll < 0 || mob->damroll > iMaxdamroll)
      ::Warning (mob, "Mob damroll of %i outside recommended range of 0 to %i",
               mob->damroll, iMaxdamroll);

    // damsizedice
    if (mob->damsizedice < 0 || mob->damsizedice > iMaxdamsizedice)
      ::Warning (mob, "Mob damsizedice of %i outside recommended range of 0 to %i",
               mob->damsizedice, iMaxdamsizedice);

    // hitnodice
    if (mob->hitnodice < 0 || mob->hitnodice > iMaxhitnodice)
      ::Warning (mob, "Mob hitnodice of %i outside recommended range of 0 to %i",
               mob->hitnodice, iMaxhitnodice);

    // hitplus
    if (mob->hitplus < 0 || mob->hitplus > iMaxhitplus)
      ::Warning (mob, "Mob hitplus of %i outside recommended range of 0 to %i",
               mob->hitplus, iMaxhitplus);

    // hitroll
    if (mob->hitroll < 0 || mob->hitroll > iMaxhitroll)
      ::Warning (mob, "Mob hitroll of %i outside recommended range of 0 to %i",
               mob->hitroll, iMaxhitroll);

    // hitsizedice
    if (mob->hitsizedice < 0 || mob->hitsizedice > iMaxhitsizedice)
      ::Warning (mob, "Mob hitsizedice of %i outside recommended range of 0 to %i",
               mob->hitsizedice, iMaxhitsizedice);

    // numattacks
    if (mob->numattacks < 0 || mob->numattacks > iMaxNumattacks)
      ::Warning (mob, "Mob numattacks of %i outside recommended range of 0 to %i",
               mob->numattacks, iMaxNumattacks);

    // saving_poison_death
    if (mob->saving_poison_death < iMinSavingthrow || mob->saving_poison_death > iMaxSavingthrow)
      ::Warning (mob, "Mob saving_poison_death of %i outside recommended range of %i to %i",
               mob->saving_poison_death, iMinSavingthrow, iMaxSavingthrow);

    // saving_wand
    if (mob->saving_wand < iMinSavingthrow || mob->saving_wand > iMaxSavingthrow)
      ::Warning (mob, "Mob saving_wand of %i outside recommended range of %i to %i",
               mob->saving_wand, iMinSavingthrow, iMaxSavingthrow);

    // saving_para_petri
    if (mob->saving_para_petri < iMinSavingthrow || mob->saving_para_petri > iMaxSavingthrow)
      ::Warning (mob, "Mob saving_para_petri of %i outside recommended range of %i to %i",
               mob->saving_para_petri, iMinSavingthrow, iMaxSavingthrow);

    // saving_breath
    if (mob->saving_breath < iMinSavingthrow || mob->saving_breath > iMaxSavingthrow)
      ::Warning (mob, "Mob saving_breath of %i outside recommended range of %i to %i",
               mob->saving_breath, iMinSavingthrow, iMaxSavingthrow);

    // saving_spell_staff
    if (mob->saving_spell_staff < iMinSavingthrow || mob->saving_spell_staff > iMaxSavingthrow)
      ::Warning (mob, "Mob saving_spell_staff of %i outside recommended range of %i to %i",
               mob->saving_spell_staff, iMinSavingthrow, iMaxSavingthrow);

    // alignment
    if (mob->alignment < iMinAlignment || mob->alignment > iMaxAlignment)
      ::Warning (mob, "Mob alignment of %i outside recommended range of %i to %i",
               mob->alignment, iMinAlignment, iMaxAlignment);

    // ===================================================================

    // check HPs, and lowest and highest
    if (mob->level >= 0 && mob->level <= 1000)
      {
      int HP = (mob->hitnodice * mob->hitsizedice) + mob->hitplus;
      pHPs [mob->level] += HP;
      pCount [mob->level] ++;
      if (IS_SET (mob->act, ACT_AGGRESSIVE))
        pAggressiveCount [mob->level] ++;
      if (HP > pHighHPs [mob->level])
        pHighHPs [mob->level] = HP;
      if (HP < pLowHPs [mob->level])
        pLowHPs [mob->level] = HP;
      }
    else
      ::Warning (mob, "Mobile has unexpected level of %i", mob->level);

    // ===================================================================

    // spell check

#ifdef SPELL_CHECKER

    if (App.m_bSpellCheckOK)    // provided loaded DLL and enabled it
      {
      CStringList strBadWordsList;

      // if the name of the item is an acceptable mis-spelling find it out now
      if (CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_ACCEPT_NAME))
         SpellCheckBlock (NULL, mob->player_name, strBadWordsList);

      if (CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_MOB_NAME))
        iSpellErrors += SpellCheckBlock (mob, mob->player_name, strBadWordsList);
      if (CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_MOB_SHORT))
        iSpellErrors += SpellCheckBlock (mob, mob->short_descr, strBadWordsList);
      if (CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_MOB_LONG))
        iSpellErrors += SpellCheckBlock (mob, mob->long_descr, strBadWordsList);
      if (CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_MOB_DESC))
        iSpellErrors += SpellCheckBlock (mob, mob->description, strBadWordsList);
      }

#endif // SPELL_CHECKER

    // ===================================================================

    // check programs
    for (POSITION progPos = mob->programlist.GetHeadPosition (); progPos; )
      {
      POSITION pos;

      program = mob->programlist.GetNext (progPos);
      iPrograms++;

      // if syntax error, add to warning list
      if (CheckCommandSyntax (program->comlist, 
                              strMessage,
                              program->xref_rooms,
                              program->xref_objects,
                              program->xref_mobs))
         ::Warning (mob, strMessage);

      // see if all cross-refenced mobs exist
      for (pos = program->xref_mobs.GetHeadPosition (); pos; )
        {
        vnum = program->xref_mobs.GetNext (pos);
        if ((refmob = FindMob (vnum)) == NULL)
          ::Warning (mob, 
            CFormat ("<%s> mobile %i not in area", 
                     (LPCTSTR) program->Summary (), vnum));
        else
          refmob->m_bReferenced = true;

        } // end of checking each mob reference

      // see if all cross-refenced objects exist
      for (pos = program->xref_objects.GetHeadPosition (); pos; )
        {
        vnum = program->xref_objects.GetNext (pos);
        if ((refobject = FindObj (vnum)) == NULL)
          ::Warning (mob, 
            CFormat ("<%s> object %i not in area", 
                     (LPCTSTR) program->Summary (), vnum));
        else
          refobject->m_bReferenced = true;
        } // end of checking each object reference

      // see if all cross-refenced rooms exist
      for (pos = program->xref_rooms.GetHeadPosition (); pos; )
        {
        vnum = program->xref_rooms.GetNext (pos);
        if ((refroom = FindRoom (vnum)) == NULL)
          ::Warning (mob, 
            CFormat ("<%s> room %i not in area", 
                     (LPCTSTR) program->Summary (), vnum));
        else
          refroom->m_bReferenced = true;
        } // end of checking each room reference

      }   // end of each program
    
    } // end of each mob

  if (nErrors - OldnErrors)
    ::Comment ("");
  ::Comment ("%i mob%s", PLURAL (m_MobList.GetCount()));
  ::Comment ("%i mob program%s", PLURAL (iPrograms));
  ::Comment ("%i spelling error%s", PLURAL (iSpellErrors));

  iTotalMobs += m_MobList.GetCount();
  iTotalPrograms += iPrograms;

  ::Comment ("");
  if (m_MobList.GetCount())
    {
    ::Comment ("Lowest  level mob was level %i", iLowLevel);
    ::Comment ("Highest level mob was level %i", iHighLevel);

    ::Comment ("");

    for (int i = 0; i <= iHighLevel; i++)
      if (pCount [i])
        {
        CString strAggressive = "";
        CString strHP;

        // let's say (aggressive), (all aggressive), or (n aggressive) depending
        if (pAggressiveCount [i] && (pCount [i] == 1))    // the only one is aggressive
          strAggressive = " (aggressive)";
        else if (pAggressiveCount [i] && (pAggressiveCount [i] == pCount [i]))
          strAggressive = " (all aggressive)";    // they are ALL bloody aggressive
        else if (pAggressiveCount [i])  // there are aggressive ones, and it isn't the only one
          strAggressive.Format (" (%i aggressive)", pAggressiveCount [i]);

        // don't bother giving averages, minima and maxima if only one
        if (pCount [i] == 1)
          strHP.Format ("HP = %i", pHPs [i]);
        else
          strHP.Format ("lowest HP = %i, average HP = %i, highest HP = %i",
                  pLowHPs [i],
                  pHPs [i] / pCount [i],
                  pHighHPs [i]);

        ::Comment ("There %s %i level %i mob%s%s, %s",
                  pCount [i] == 1 ? "is" : "are",
                  pCount [i],
                  i,
                  pCount [i] == 1 ? "" : "s",
                  (LPCTSTR) strAggressive,
                  (LPCTSTR) strHP
                  );
        } // end of having a mob at this level

    }

  ::Comment ("");
  if (nErrors - OldnErrors)
    ::Comment ("** %i problem%s in mobs", PLURAL (nErrors - OldnErrors));
  else
    ::Comment ("No problems in mobs");

  // process each room

  ::Comment ("");
  ::Comment ("Checking rooms");
  ::Comment ("");
  iPrograms = 0;
  iExits = 0;
  OldnErrors = nErrors;

  iSpellErrors = 0;
  for (RoomPos = m_RoomList.GetHeadPosition (); RoomPos; )
    {
    room = m_RoomList.GetNext (RoomPos);

    if (iMilestone++ % 32 == 0)
      Frame.UpdateProgressBar (iMilestone);

    if (bWarnVnumsOutOfRange)
      if (room->vnum < 1 || room->vnum > iMaxVnum)
        ::Warning (room, "Room vnum of %i outside recommended range of 1 to %i",
                 room->vnum, iMaxVnum);

    for (POSITION progPos = room->programlist.GetHeadPosition (); progPos; )
      {
      POSITION pos;

      program = room->programlist.GetNext (progPos);
      iPrograms++;

      // if syntax error, add to warning list
      if (CheckCommandSyntax (program->comlist, 
                              strMessage,
                              program->xref_rooms,
                              program->xref_objects,
                              program->xref_mobs))
         ::Warning (room, strMessage);

      // see if all cross-refenced mobs exist
      for (pos = program->xref_mobs.GetHeadPosition (); pos; )
        {
        vnum = program->xref_mobs.GetNext (pos);
        if ((refmob = FindMob (vnum)) == NULL)
          ::Warning (room, 
            CFormat ("<%s> mobile %i not in area", 
                     (LPCTSTR) program->Summary (), vnum));
        else
          refmob->m_bReferenced = true;
        } // end of checking each mob reference

      // see if all cross-refenced objects exist
      for (pos = program->xref_objects.GetHeadPosition (); pos; )
        {
        vnum = program->xref_objects.GetNext (pos);
        if ((refobject = FindObj (vnum)) == NULL)
          ::Warning (room, 
            CFormat ("<%s> object %i not in area", 
                     (LPCTSTR) program->Summary (), vnum));
        else
          refobject->m_bReferenced = true;
        } // end of checking each object reference

      // see if all cross-refenced rooms exist
      for (pos = program->xref_rooms.GetHeadPosition (); pos; )
        {
        vnum = program->xref_rooms.GetNext (pos);
        if ((refroom = FindRoom (vnum)) == NULL)
          ::Warning (room, 
            CFormat ("<%s> room %i not in area", 
                     (LPCTSTR) program->Summary (), vnum));
        else
          refroom->m_bReferenced = true;
        } // end of checking each room reference

      }   // end of each program
    
    // check all exits

    for (pos = room->exitlist.GetHeadPosition (); pos; )
      {
      exit = room->exitlist.GetNext (pos);
      iExits++;

      if (exit->key > 0)    // NB - maybe should check for -1, but some seem to be zero
        {
        iKeys++;
        if ((refobject = FindObj (exit->key)) == NULL)
          ::Warning (exit, 
            CFormat ("Key (object %i) to exit not in area", 
                      exit->key));
        else
          refobject->m_bReferenced = true;
        }

      if (exit->vnum != -1)   // a vnum of -1 means an exit that doesn't lead anywhere
        if ((refroom = FindRoom (exit->vnum)) == 0)
          ::Warning (exit, CFormat ("from room #%i", room->vnum));   // exit message will say "not in area"
        else
          refroom->m_bReferenced = true;
      }   // end of processing each exit

    if (room->tele_vnum > 0)
      if ((refroom = FindRoom (room->tele_vnum)) == NULL)
        ::Warning (room, 
          CFormat ("Teleport room %i not in area", 
                    room->tele_vnum));
      else
        refroom->m_bReferenced = true;

    // ===================================================================

    // spell check

#ifdef SPELL_CHECKER

    if (App.m_bSpellCheckOK)    // provided loaded DLL and enabled it
      {
      CStringList strBadWordsList;

      if (CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_ROOM_NAME))
        iSpellErrors += SpellCheckBlock (room, room->name, strBadWordsList);
      if (CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_ROOM_DESC))
        iSpellErrors += SpellCheckBlock (room, room->description, strBadWordsList);

      // now do the extra descriptions
      for (POSITION extraPos = room->extralist.GetHeadPosition (); extraPos; )
        {
        CExtraDescription * extra = room->extralist.GetNext (extraPos);
        if (CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_EXTRA_DESC))
          iSpellErrors += SpellCheckBlock (room, extra->description, strBadWordsList);
        }

      }   // end of spell check OK

#endif // SPELL_CHECKER

    } // end of each room

  if (nErrors - OldnErrors)
    ::Comment ("");

  ::Comment ("%i room%s", PLURAL (m_RoomList.GetCount()));
  ::Comment ("%i room program%s", PLURAL (iPrograms));
  ::Comment ("%i room exit%s", PLURAL (iExits));
  ::Comment ("%i spelling error%s", PLURAL (iSpellErrors));

  iTotalRooms += m_RoomList.GetCount();
  iTotalPrograms += iPrograms;
  iTotalExits += iExits;

  if (iKeys)
    ::Comment ("%i exit%s need%s a key", 
             PLURAL (iKeys),
             iKeys == 1 ? "s" : "");
  if (nErrors - OldnErrors)
    ::Comment ("** %i problem%s in rooms", PLURAL (nErrors - OldnErrors));
  else
    ::Comment ("No problems in rooms");

  // process each object

  ::Comment ("");
  ::Comment ("Checking objects");
  ::Comment ("");
  iPrograms = 0;
  OldnErrors = nErrors;
  iSpellErrors = 0;

  for (ObjectPos = m_ObjectList.GetHeadPosition (); ObjectPos; )
    {
    object = m_ObjectList.GetNext (ObjectPos);

    if (iMilestone++ % 32 == 0)
      Frame.UpdateProgressBar (iMilestone);

    if (bWarnVnumsOutOfRange)
      if (object->vnum < 1 || object->vnum > iMaxVnum)
        ::Warning (object, "Object vnum of %i outside recommended range of 1 to %i",
                 object->vnum, iMaxVnum);

    for (POSITION progPos = object->programlist.GetHeadPosition (); progPos; )
      {
      POSITION pos;

      program = object->programlist.GetNext (progPos);
      iPrograms++;

      // if syntax error, add to warning list
      if (CheckCommandSyntax (program->comlist, 
                              strMessage,
                              program->xref_rooms,
                              program->xref_objects,
                              program->xref_mobs))
         ::Warning (object, strMessage);

      // see if all cross-refenced mobs exist
      for (pos = program->xref_mobs.GetHeadPosition (); pos; )
        {
        vnum = program->xref_mobs.GetNext (pos);
        if ((refmob = FindMob (vnum)) == NULL)
          ::Warning (object, 
            CFormat ("<%s> mobile %i not in area", 
                     (LPCTSTR) program->Summary (), vnum));
        else
          refmob->m_bReferenced = true;
        } // end of checking each mob reference

      // see if all cross-refenced objects exist
      for (pos = program->xref_objects.GetHeadPosition (); pos; )
        {
        vnum = program->xref_objects.GetNext (pos);
        if ((refobject = FindObj (vnum)) == NULL)
          ::Warning (object, 
            CFormat ("<%s> object %i not in area", 
                     (LPCTSTR) program->Summary (), vnum));
        else
          refobject->m_bReferenced = true;
        } // end of checking each object reference

      // see if all cross-refenced rooms exist
      for (pos = program->xref_rooms.GetHeadPosition (); pos; )
        {
        vnum = program->xref_rooms.GetNext (pos);
        if ((refroom = FindRoom (vnum)) == NULL)
          ::Warning (object, 
            CFormat ("<%s> room %i not in area", 
                     (LPCTSTR) program->Summary (), vnum));
        else
          refroom->m_bReferenced = true;
        } // end of checking each room reference

      }   // end of each program

    // ===================================================================

    // spell check
#ifdef SPELL_CHECKER

    if (App.m_bSpellCheckOK)    // provided loaded DLL and enabled it
      {
      CStringList strBadWordsList;

      // if the name of the item is an acceptable mis-spelling find it out now
      if (CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_ACCEPT_NAME))
         SpellCheckBlock (NULL, object->name, strBadWordsList);

      if (CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_OBJECT_NAME))
        iSpellErrors += SpellCheckBlock (object, object->name, strBadWordsList);
      if (CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_OBJECT_SHORT))
        iSpellErrors += SpellCheckBlock (object, object->short_descr, strBadWordsList);
      if (CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_OBJECT_ACTIONDESC))
        iSpellErrors += SpellCheckBlock (object, object->action_desc, strBadWordsList);
      if (CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_OBJECT_LONG))
        iSpellErrors += SpellCheckBlock (object, object->description, strBadWordsList);

      // now do the extra descriptions
      for (POSITION extraPos = object->extralist.GetHeadPosition (); extraPos; )
        {
        CExtraDescription * extra = object->extralist.GetNext (extraPos);
        if (CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_EXTRA_DESC))
          iSpellErrors += SpellCheckBlock (object, extra->description, strBadWordsList);
        }

      }     // end of spellcheck wanted
    
#endif // SPELL_CHECKER

    } // end of each object

  if (nErrors - OldnErrors)
    ::Comment ("");

  ::Comment ("%i object%s", PLURAL (m_ObjectList.GetCount()));
  ::Comment ("%i object program%s", PLURAL (iPrograms));
  ::Comment ("%i spelling error%s", PLURAL (iSpellErrors));

  iTotalObjects += m_ObjectList.GetCount();
  iTotalPrograms += iPrograms;

  if (nErrors - OldnErrors)
    ::Comment ("** %i problem%s in objects", PLURAL (nErrors - OldnErrors));
  else
    ::Comment ("No problems in objects");

    // process each reset

  ::Comment ("");
  ::Comment ("Checking resets");
  ::Comment ("");
  OldnErrors = nErrors;

  for (pos = m_ResetList.GetHeadPosition (); pos; )
    {
    reset = m_ResetList.GetNext (pos);

    if (iMilestone++ % 32 == 0)
      Frame.UpdateProgressBar (iMilestone);

    switch (reset->command)
	    {
	    default:
          ::Warning (reset, "Bad command '%c'.",  reset->command);
	        return;

	    case 'M':
	        if ((refmob = FindMob ( reset->arg1 )) == NULL)
		        ::Warning (reset, "");
          else
            refmob->m_bReferenced = true;
	        if ((refroom = FindRoom ( reset->arg3 )) == NULL)
		        ::Warning (reset, "");
          else
            refroom->m_bReferenced = true;
	        break;

	    case 'O':
	        if ((refobject = FindObj(reset->arg1)) == NULL)
		        ::Warning (reset, "");
          else
            refobject->m_bReferenced = true;
	        if ((refroom = FindRoom (reset->arg3)) == NULL)
		        ::Warning (reset, "");
          else
            refroom->m_bReferenced = true;

	        break;

	    case 'P':
	        if ((refobject = FindObj(reset->arg1)) == NULL)
		        ::Warning (reset, "");
          else
            refobject->m_bReferenced = true;
	        if (reset->arg3 > 0 )
		        if ((refobject = FindObj(reset->arg3)) == NULL)
		            ::Warning (reset, "");
          else
            refobject->m_bReferenced = true;
	        break;

	    case 'G':
	    case 'E':
	        if ((refobject =  FindObj(reset->arg1)) == NULL)
		        ::Warning (reset, "");
          else
            refobject->m_bReferenced = true;
	        break;

	    case 'T':
	        break;

	    case 'H':
	        if ( reset->arg1 > 0 )
		        if ((refobject =  FindObj(reset->arg1)) == NULL)
		            ::Warning (reset, "");
          else
            refobject->m_bReferenced = true;
	        break;

	    case 'D':
	        room = FindRoom ( reset->arg1 );
	        if ( !room )
	        {
		       ::Warning (reset, "");
		      break;
	        }
          else
            room->m_bReferenced = true;

	        if ( reset->arg2 < 0
	        ||   reset->arg2 > MAX_DIR+1
	        || ( exit = get_exit(room, reset->arg2)) == NULL
	        || !IS_SET( exit->exit_info, EX_ISDOOR ) )
	        {
		       ::Warning (reset,  "Exit %i not door.",
			                   reset->arg2 );
	        }

	        if ( reset->arg3 < 0 || reset->arg3 > 2 )
	        {
		      ::Warning (reset,  "Bad 'locks': %i.",
			     reset->arg3 );
	        }
	        break;

	    case 'R':
	        room = FindRoom ( reset->arg1 );
	        if ( !room)
            ::Warning (reset, "");
          else
            room->m_bReferenced = true;

	        if ( reset->arg2 < 0 || reset->arg2 > 6 )
	        {
		      ::Warning (reset,  "Bad exit %i.",
			     reset->arg2 );
		      break;
	        }

	        break;
	    }   // end of switch
  }   // end of processing each reset

  if (nErrors - OldnErrors)
    ::Comment ("");

  ::Comment ("%i reset%s", PLURAL (m_ResetList.GetCount()));

  if (nErrors - OldnErrors)
    ::Comment ("** %i problem%s in resets", PLURAL (nErrors - OldnErrors));
  else
    ::Comment ("No problems in resets");

// check each shop

  if (!m_ShopList.IsEmpty ())
    {
    ::Comment ("");
    ::Comment ("Checking shops");
    ::Comment ("");
    OldnErrors = nErrors;

    for (pos = m_ShopList.GetHeadPosition (); pos; )
      {
      shop = m_ShopList.GetNext (pos);
      if (iMilestone++ % 32 == 0)
        Frame.UpdateProgressBar (iMilestone);

      // check keeper in area
 	    mob		= FindMob ( shop->keeper );
      if (!mob)
		    ::Warning(shop, "Mobile %i is not in area.", shop->keeper );

      }   // end of processing each shop


    if (nErrors - OldnErrors)
      ::Comment ("");
    
    ::Comment ("%i shop%s", PLURAL (m_ShopList.GetCount()));
  
    if (nErrors - OldnErrors)
      ::Comment ("** %i problem%s in shops", PLURAL (nErrors - OldnErrors));
    else
      ::Comment ("No problems in shops");
    }

// check all repairs

  if (!m_RepairList.IsEmpty ())
    {
    ::Comment ("");
    ::Comment ("Checking repairs");
    ::Comment ("");
    OldnErrors = nErrors;

    // cycle through all repairs

    for (pos = m_RepairList.GetHeadPosition (); pos; )
      {
      repair = m_RepairList.GetNext (pos);
      if (iMilestone++ % 32 == 0)
        Frame.UpdateProgressBar (iMilestone);

      // check keeper in area
	    mob		= FindMob ( repair->keeper );
      if (!mob)
		    ::Warning(repair, "Mobile %i is not in area.", repair->keeper );

      }   // end of processing each repair

      if (nErrors - OldnErrors)
        ::Comment ("");

      ::Comment ("%i repair%s", PLURAL (m_RepairList.GetCount()));

      if (nErrors - OldnErrors)
        ::Comment ("** %i problem%s in repairs", PLURAL (nErrors - OldnErrors));
      else
        ::Comment ("No problems in repairs");
      }


// check each help

#ifdef SPELL_CHECKER

  if (App.m_bSpellCheckOK && 
      CHECK_BIT_NUMBER (App.m_SpellCheckOptions, SK_HELP_TEXT))
    {
    iSpellErrors = 0;
    CStringList strBadWordsList;

    if (!m_HelpList.IsEmpty ())
      {
      ::Comment ("");
      ::Comment ("Checking helps");
      ::Comment ("");
      OldnErrors = nErrors;

      for (pos = m_HelpList.GetHeadPosition (); pos; )
        {
        CHelp * help = m_HelpList.GetNext (pos);
        if (iMilestone++ % 32 == 0)
          Frame.UpdateProgressBar (iMilestone);


        iSpellErrors += SpellCheckBlock (help, help->text, strBadWordsList);

        }   // end of processing each help


      if (nErrors - OldnErrors)
        ::Comment ("");
    
      ::Comment ("%i help%s", PLURAL (m_HelpList.GetCount()));
      ::Comment ("%i spelling error%s", PLURAL (iSpellErrors));
      
      if (nErrors - OldnErrors)
        ::Comment ("** %i problem%s in helps", PLURAL (nErrors - OldnErrors));
      else
        ::Comment ("No problems in helps");
      }   // end of having helps
    } // end of spell check on help wanted

#endif // SPELL_CHECKER

  // delete HP and count arrays

  delete [] pHPs;
  delete [] pLowHPs;
  delete [] pHighHPs;
  delete [] pCount;
  delete [] pAggressiveCount;

  Frame.RemoveProgressBar ();

	
}

void CAreaEditorDoc::CrossReferenceCheck() 
  {
POSITION mobPos;
POSITION RoomPos;
POSITION ObjectPos;

  CMobile * mob;
  CMUDObject * object;
  CRoom * room;

  for (mobPos = m_MobList.GetHeadPosition (); mobPos; )
    if (!(mob = m_MobList.GetNext (mobPos))->m_bReferenced)
      ::Warning (mob, "Not used.");

  for (RoomPos = m_RoomList.GetHeadPosition (); RoomPos; )
    if (!(room = m_RoomList.GetNext (RoomPos))->m_bReferenced)
      ::Warning (room, "Not used.");

  for (ObjectPos = m_ObjectList.GetHeadPosition (); ObjectPos; )
    if (!(object = m_ObjectList.GetNext (ObjectPos))->m_bReferenced)
      ::Warning (object, "Not used.");
  }



void CheckAreas (CAreaEditorDoc * pDoc)
  {
POSITION mobPos;
POSITION RoomPos;
POSITION ObjectPos;
POSITION commandPos;
POSITION socialPos;
POSITION skillPos;

CCommand * command;
CSocial * social;
CSkill * skill;
CProgressDlg * pProgressDlg = NULL;
int iCount = 0,
    iTotal = 0;

int OldnErrors = 0;

int iTotalRooms = 0;
int iTotalMobs = 0;
int iTotalObjects = 0;
int iTotalExits = 0;
int iTotalPrograms = 0;

  strMessage.Empty ();
  nErrors = 0;
  iTotalPrograms = 0;

  // clear all "referenced" flags

  for (POSITION docPos = App.m_pDocTemplate->GetFirstDocPosition();
      docPos != NULL; )
    {

    CAreaEditorDoc * pDoc = (CAreaEditorDoc *) App.m_pDocTemplate->GetNextDoc(docPos);

    for (mobPos = pDoc->m_MobList.GetHeadPosition (); mobPos; )
      pDoc->m_MobList.GetNext (mobPos)->m_bReferenced = false;

    for (RoomPos = pDoc->m_RoomList.GetHeadPosition (); RoomPos; )
      pDoc->m_RoomList.GetNext (RoomPos)->m_bReferenced = false;

    for (ObjectPos = pDoc->m_ObjectList.GetHeadPosition (); ObjectPos; )
      pDoc->m_ObjectList.GetNext (ObjectPos)->m_bReferenced = false;

    } // end of doing each document


  // clear all program reference counts

  for (commandPos = App.m_CommandList.GetHeadPosition (); commandPos; )
    App.m_CommandList.GetNext (commandPos)->iReferenced = 0;

  for (socialPos = App.m_SocialList.GetHeadPosition (); socialPos; )
    App.m_SocialList.GetNext (socialPos)->iReferenced = 0;

  for (skillPos = App.m_SkillList.GetHeadPosition (); skillPos; )
    App.m_SkillList.GetNext (skillPos)->iReferenced = 0;


  if (pDoc)
    pDoc->AreaCheck (iTotalRooms,
                     iTotalMobs,
                     iTotalObjects,
                     iTotalExits,
                     iTotalPrograms);   // check this area
  else
    {
    POSITION docPos;

    // count number of documents
    for (docPos = App.m_pDocTemplate->GetFirstDocPosition();
        docPos != NULL; iTotal++)
      App.m_pDocTemplate->GetNextDoc(docPos);

    pProgressDlg = new CProgressDlg;
    pProgressDlg->Create ();
    pProgressDlg->SetRange (0, iTotal);     
    pProgressDlg->SetWindowText ("Checking areas ...");                              

    for (docPos = App.m_pDocTemplate->GetFirstDocPosition();
        docPos != NULL; )
      {

      CAreaEditorDoc * pDoc = (CAreaEditorDoc *) App.m_pDocTemplate->GetNextDoc(docPos);

      CString strName = pDoc->GetTitle ();

      if (pDoc->m_Area && !pDoc->m_Area->strAreaName.IsEmpty ())
        strName = pDoc->m_Area->strAreaName;

      ::Comment ("");
      ::Comment ("--------------- Area: %s ---------------", strName);
      ::Comment ("");

      pProgressDlg->SetStatus (CFormat ("Checking %s ...", (LPCTSTR) strName));    // area name
      pProgressDlg->SetPos (iCount++);   // which one

      pDoc->AreaCheck (iTotalRooms,
                       iTotalMobs,
                       iTotalObjects,
                       iTotalExits,
                       iTotalPrograms);   // check this area

      // if they cancel, exit read loop
      if (pProgressDlg->CheckCancelButton())
        {
        Comment (" (cancelled by user)");
        break;
        }

      }   // end of doing each document

    ::Comment ("");
    ::Comment ("------------- (end of areas) -------------");
    ::Comment ("");

    }   // end of wanting all areas

  ::Comment ("");
  ::Comment ("-------- Checking everything is used ---------");
  ::Comment ("");
  OldnErrors = nErrors;

// check that each room/mob/object is cross-referenced somewhere

  if (pProgressDlg)
    pProgressDlg->SetStatus ("Checking all cross-references"); // warn them of delay

  if (pDoc)
    pDoc->CrossReferenceCheck ();   // check this area
  else
    {
    Frame.CreateProgressBar ("Cross-referencing", iTotal);
    iCount = 0;

    for (POSITION docPos = App.m_pDocTemplate->GetFirstDocPosition();
        docPos != NULL; )
      {

      CAreaEditorDoc * pDoc = (CAreaEditorDoc *) App.m_pDocTemplate->GetNextDoc(docPos);

      CString strName = pDoc->GetTitle ();

      if (pDoc->m_Area && !pDoc->m_Area->strAreaName.IsEmpty ())
        strName = pDoc->m_Area->strAreaName;

      ::Comment ("");
      ::Comment ("--------------- Area: %s ---------------", strName);
      ::Comment ("");

      Frame.UpdateProgressBar (iCount++);

      pDoc->CrossReferenceCheck ();   // check this area

      }   // end of doing each document

    ::Comment ("");
    ::Comment ("------------- (end of areas) -------------");
    ::Comment ("");

    Frame.RemoveProgressBar ();

    }   // end of wanting all areas

  if (nErrors - OldnErrors)
    {
    ::Comment ("");
    ::Comment ("** %i item%s not used", PLURAL (nErrors - OldnErrors));
    if (iTotalPrograms)
      if (App.m_CommandList.IsEmpty () 
       || App.m_SocialList.IsEmpty ()
       || App.m_SkillList.IsEmpty ())
        ::Comment ("** Cross-referencing of items is likely to be inaccurate "
                 "because commands/socials/skills file(s) not loaded.");
    }
  else
    ::Comment ("No unused items found.");

  if (iTotalPrograms &&
      !App.m_CommandList.IsEmpty () &&
      !App.m_SocialList.IsEmpty () &&
      !App.m_SkillList.IsEmpty ())
    {
    ::Comment ("");
    ::Comment ("Commands used");
    ::Comment ("");
    iCount = 0;

    for (commandPos = App.m_CommandList.GetHeadPosition (); commandPos; )
      {
      command = App.m_CommandList.GetNext (commandPos);
      if (command->iReferenced)
        {
        ::Comment ("%s - %i time%s", 
                 (LPCTSTR) command->name,
                 PLURAL (command->iReferenced));
        iCount++;
        }
      }

    if (iCount)
      {
      ::Comment ("");
      ::Comment ("%i (different) MUD program command%s used", PLURAL (iCount));
      }
    else
      ::Comment ("No MUD program commands used.");

    ::Comment ("");
    ::Comment ("Socials used");
    ::Comment ("");
    iCount = 0;

    for (socialPos = App.m_SocialList.GetHeadPosition (); socialPos; )
      {
      social = App.m_SocialList.GetNext (socialPos);
      if (social->iReferenced)
        {
        ::Comment ("%s - %i time%s", 
                 (LPCTSTR) social->name,
                 PLURAL (social->iReferenced));
        iCount++;
        }
      }

    if (iCount)
      {
      ::Comment ("");
      ::Comment ("%i (different) social%s used", PLURAL (iCount));
      }
    else
      ::Comment ("No socials used in programs.");

    ::Comment ("");
    ::Comment ("Spells used in programs");
    ::Comment ("");
    iCount = 0;

    for (skillPos = App.m_SkillList.GetHeadPosition (); skillPos; )
      {
      skill = App.m_SkillList.GetNext (skillPos);
      if (skill->type == SKILL_SPELL && skill->iReferenced)
        {
        ::Comment ("%s - %i time%s", 
                 (LPCTSTR) skill->name,
                 PLURAL (skill->iReferenced));
        iCount++;
        }
      }

    if (iCount)
      {
      ::Comment ("");
      ::Comment ("%i (different) spell%s used", PLURAL (iCount));
      }
    else
      ::Comment ("No spells used in programs.");

    ::Comment ("");
    ::Comment ("Skills used in programs");
    ::Comment ("");
    iCount = 0;

    for (skillPos = App.m_SkillList.GetHeadPosition (); skillPos; )
      {
      skill = App.m_SkillList.GetNext (skillPos);
      if (skill->type == SKILL_SKILL && skill->iReferenced)
        {
        ::Comment ("%s - %i time%s", 
                 (LPCTSTR) skill->name,
                 PLURAL (skill->iReferenced));
        iCount++;
        }
      }

    if (iCount)
      {
      ::Comment ("");
      ::Comment ("%i (different) skill%s used", PLURAL (iCount));
      }
    else
      ::Comment ("No skills used in programs.");

    }   // end of commands/socials/skills loaded

  ::Comment ("");
  ::Comment ("%i total warning%s.", PLURAL (nErrors));

  ::Comment ("");
  ::Comment ("-- Totals --");
  ::Comment ("");

  ::Comment ("%i room%s", PLURAL (iTotalRooms));
  ::Comment ("%i mobile%s", PLURAL (iTotalMobs));
  ::Comment ("%i object%s", PLURAL (iTotalObjects));
  ::Comment ("%i exit%s", PLURAL (iTotalExits));
  ::Comment ("%i program%s", PLURAL (iTotalPrograms));

  ::Comment ("");
  ::Comment ("End of area check.");

  ::Comment ("");
  ::Comment ("Press <Esc> to close this window.");

  delete pProgressDlg;

  /*

  // MODAL way of doing it

  CAreaLoadingProblems dlg;

  dlg.m_strTitle = "Area check results";

  dlg.m_strErrors = strMessage;

  dlg.DoModal ();

  */

  // MODELESS dialog here

  CAreaLoadingProblems * dlg = new CAreaLoadingProblems;
  dlg->m_strTitle = "Area check results";   // set up dialog title
  dlg->m_strErrors = strMessage;            // what it says
  dlg->Create (ID_PROBLEMS_LOADING_AREA, NULL);  // create it
  dlg->ShowWindow(SW_SHOW);  // and, finally, show it - dialog will delete itself

  }



void CAreaEditorDoc::OnAreaCheck() 
  {
// first - update the current item

// find our splitter view

 for (POSITION pos = GetFirstViewPosition(); pos;)
   {
    CView* pView = GetNextView(pos);

    // if the splitter view, update current pane
    if (pView->IsKindOf(RUNTIME_CLASS(CAreaEditorView)))
      {
      pView = (CView *) ((CAreaEditorView *)pView)->m_pSplitterWindow->GetPane(0,1);
      if (!pView->UpdateData (TRUE))
        return;
      }
   }   


  CheckAreas (this);

  }