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 "AreaEditorDoc.h"

static char * pWord,
            * pWordStart;
static long nLineNumber;
static int nGrownAmount;
static int iWordLength; // length of most recent word

// amount by which we can grow the command buffer (by changing vnums)

#define GROWSIZE 1000

// ----------------------------- stuff for if checks ----------------------------

CStringList lDirections;

typedef enum { 
       eCondNone = 0, // no special processing
       eCondObject,   // object name
       eCondRoom,     // room name
       eCondMobile,    // mob name
       eCondDirection   // eg. n, s, e, w etc.
      } t_condition_type;

typedef struct 
  {
  char * sFunctionName;
  t_condition_type Arg;  // condition argument is object/room/mobile, eg. mobinroom (1234)
  t_condition_type Value;   // condition compares to object/room/mobile, eg. inroom ($n) == 1234
  bool bBoolean;    // true for boolean checks, eg. isnpc ($n) 
  } t_specialconditions;

const t_specialconditions SpecialConditions [] =
  {

    // no vnums
    { "rand", eCondNone, eCondNone, true  },
    { "otypehere" },
    { "otyperoom" },
    { "otypecarry" },
    { "otypewear" },
    { "otypeinv" },
    { "number" },
    { "name" },
    

    // room argument, eg. mobinroom (10300) > 4
    { "economy", eCondRoom }, 
    { "mobinroom", eCondRoom }, 

    // mob argument
    { "timeskilled", eCondMobile }, 
    { "ismobinvis", eCondMobile, eCondNone, true },
    { "mobinvislevel", eCondMobile },
    { "ispc", eCondMobile, eCondNone, true },
    { "isnpc", eCondMobile, eCondNone, true },
    { "ispkill", eCondMobile, eCondNone, true },
    { "isdevoted", eCondMobile, eCondNone, true },
    { "canpkill", eCondMobile, eCondNone, true },
    { "ismounted", eCondMobile, eCondNone, true },
    { "isgood", eCondMobile, eCondNone, true },
    { "isneutral", eCondMobile, eCondNone, true },
    { "isevil", eCondMobile, eCondNone, true },
    { "isfight", eCondMobile, eCondNone, true },
    { "isimmort", eCondMobile, eCondNone, true },
    { "ischarmed", eCondMobile, eCondNone, true },
    { "isfollow", eCondMobile, eCondNone, true },
    { "isaffected", eCondMobile },

    { "hitprcnt", eCondMobile }, 
    { "inroom", eCondMobile, eCondRoom }, 
    { "wasinroom", eCondMobile, eCondRoom }, 
    { "norecall", eCondMobile }, 
    { "sex", eCondMobile }, 
    { "position", eCondMobile }, 
    { "doingquest", eCondMobile }, 
    { "ishelled", eCondMobile }, 
    { "level", eCondMobile }, 
    { "goldamt", eCondMobile }, 
    { "class", eCondMobile }, 
    { "race", eCondMobile }, 
    { "clan", eCondMobile }, 
    { "deity", eCondMobile }, 
    { "guild", eCondMobile }, 
    { "clantype", eCondMobile }, 
    { "favor", eCondMobile }, 
    { "str", eCondMobile }, 
    { "wis", eCondMobile }, 
    { "int", eCondMobile }, 
    { "dex", eCondMobile }, 
    { "con", eCondMobile }, 
    { "cha", eCondMobile }, 
    { "lck", eCondMobile }, 

    // extensions for Brian Carter
    { "iswearing", eCondMobile, eCondObject }, 
    { "iscarrying", eCondMobile, eCondObject }, 

    // object argument, eg. objtype (1234) == 1
    { "ovnumhere", eCondObject }, 
    { "ovnumroom",  eCondObject }, 
    { "ovnumwear", eCondObject }, 
    { "ovnumcarry", eCondObject }, 
    { "ovnuminv", eCondObject }, 
    { "objtype", eCondObject }, 
    { "objval0", eCondObject }, 
    { "objval1", eCondObject }, 
    { "objval2", eCondObject }, 
    { "objval3", eCondObject }, 
    { "objval4", eCondObject }, 
    { "objval5", eCondObject }, 

// direction tests for Brian Carter

    { "isclosed", eCondDirection, eCondNone, true },
    { "islocked", eCondDirection, eCondNone, true },
    { "isopen", eCondDirection, eCondNone, true },
    { "isunlocked", eCondDirection, eCondNone, true },

  };

// ------------------------------ end of if check stuff ----------------------------

static bool skip_spaces (void)
  {
  for ( ; *pWord && isspace (*pWord); pWord++)
    if (*pWord == '\n')
      return true;    // end of line reached

  return false;
  } // end of skip_spaces

static void fread_to_eol (void)
  {
  for ( ; *pWord && *pWord != '\n'; pWord++)
    ;

  if (*pWord == '\n')
    pWord++;    // skip end-of-line

  nLineNumber++;    // count lines too
  } // end of fread_to_eol

static CString fread_word (bool bQuoted = true)
  {
CString strResult;

  iWordLength = 0;

  if (skip_spaces ())
    return "";        // stop at end of line
  
  if (*pWord == 0)    // stop at end of file
    return "";

  char cEnd = ' ';

  if (bQuoted)
    {
    cEnd = *pWord;    // remember as trailing delimiter
	  if (cEnd == '\'' || cEnd == '"')
		  pWord++;
    else
      cEnd = ' ';
    }

  pWordStart = pWord;   // remember where word starts

  // a non-alpha start sounds like 'Hi there
  if (!bQuoted && !isalnum (*pWord))
    {
    pWord++;
    iWordLength++;
    strResult = CString (pWordStart, 1);
    }
  else
    {
    for (; *pWord; pWord++, iWordLength++)
      if (cEnd == ' ' ? isspace(*pWord) : *pWord == cEnd )
        break;
    strResult = CString (pWordStart, pWord - pWordStart);
    if (*pWord)
      pWord++;    // skip trailing delimiter
    }

  return strResult;

  } // end of fread_word

static CString fread_condition_word (void)
  {

  iWordLength = 0;

  if (skip_spaces ())
    return "";        // stop at end of line
  
  if (*pWord == 0)    // stop at end of file
    return "";

  pWordStart = pWord;   // remember where word starts

  char c;

  for ( ; c = *pWord; pWord++, iWordLength++)
    {
    if (c == '(' || 
        c == ')' || 
        isspace (c))
      break;
    }

  // if we stopped on 1st character, it must have been a bracket
  if (pWord == pWordStart)
    {
    pWord++;
    iWordLength++;
    }

  CString strResult = CString (pWordStart, pWord - pWordStart);

  return strResult;

  } // end of fread_condition_word

static CString fread_condition_operator (void)
  {

  iWordLength = 0;

  if (skip_spaces ())
    return "";        // stop at end of line
  
  if (*pWord == 0)    // stop at end of file
    return "";

  pWordStart = pWord;   // remember where word starts

  char c;

  for ( ; c = *pWord; pWord++, iWordLength++)
    {
    if (isalnum (c) || isspace (c))
      break;
    }

  CString strResult = CString (pWordStart, pWord - pWordStart);

  return strResult;

  } // end of fread_condition_operator

static void FixProgramVnum (const int vnum, 
                           const int * pOldVnums, 
                           const int * pNewVnums,
                           const int iVnumCount)
  {
  CString strNewVnum;

  int newvnum = vnum;

  // do the vnum lookup and see if we get a different one

  FixupVnum (newvnum, pOldVnums, pNewVnums, iVnumCount);

  // no change, don't do anything more
  if (newvnum == vnum)
    return;

  int iOldLength = iWordLength;  // how long the number *was*, excluding delimiter
  strNewVnum.Format ("%i", newvnum);        // new number
  int iNewLength = strNewVnum.GetLength (); // how long it is
  int iDifference = iNewLength - iOldLength;// length difference  

  // count amount we grow
  nGrownAmount += iDifference;

  if (nGrownAmount > GROWSIZE)
    ThrowErrorException ("Insufficient internal buffer space to renumber program vnums");

  // if necessary, shuffle rest of buffer along to allow for new vnum length
  if (iDifference && strlen (pWordStart + iOldLength))
    {
    memmove (pWordStart + iNewLength, 
             pWordStart + iOldLength, 
             strlen (pWordStart + iOldLength) + 1);  // add 1 to take null with us
    pWord += iDifference;    // adjust word pointer for *next* word we process
    }

  // move new vnum into position
  memcpy (pWordStart, (const char *) strNewVnum, strNewVnum.GetLength ());

  } // end of FixProgramVnum


static void HandleCondition (t_prenumber pRenumberStuff)
  {

int vnum = 0;
int i;
const t_specialconditions * pCondition = NULL;

char * pWordOrig = pWord;   // save word pointer

  // get the condition name
CString strCondition = fread_condition_word ();

  strCondition.TrimLeft ();
  strCondition.TrimRight ();
  strCondition.MakeLower ();

  // search table for if checks that match this one
  for (i = 0; i < NUMITEMS (SpecialConditions); i++)
    if (strCondition == SpecialConditions [i].sFunctionName)
      pCondition = &SpecialConditions [i];

  if (!pCondition)
    ThrowErrorException ("Unknown IF test \"%s\"",
                         (LPCTSTR) strCondition);

  if (fread_condition_word () != '(')
    ThrowErrorException ("No \"(\" symbol");
  
CString strArg = fread_condition_word ();

  if (strArg.IsEmpty ())
    ThrowErrorException ("No argument supplied in brackets");

  strArg.MakeLower ();

  if (strArg [0] == '$')
    {
    if (strArg.GetLength () < 2)
      ThrowErrorException ("$ must be followed by a letter");

    if (strchr ("introp", strArg [1]) == NULL)
      ThrowErrorException ("Bad argument character \"%s\"",
                            (LPCTSTR) strArg);

    } // end of $n argument
  else if (pCondition->Arg == eCondDirection)
    {
    if (!lDirections.Find (strArg))
      ThrowErrorException ("Bad direction \"%s\"",
                            (LPCTSTR) strArg);
    }
  else
    { // must be a vnum?
    vnum = atoi (strArg);
  
    if (vnum && pRenumberStuff)
      {
      switch (pCondition->Arg)
        {
        case eCondMobile:
            FixProgramVnum (vnum, 
                            pRenumberStuff->pOldMobVnum, 
                            pRenumberStuff->pNewMobVnum, 
                            pRenumberStuff->iMobCount);
            break;
        case eCondRoom:
            FixProgramVnum (vnum, 
                            pRenumberStuff->pOldRoomVnum, 
                            pRenumberStuff->pNewRoomVnum, 
                            pRenumberStuff->iRoomCount);
            break;
        case eCondObject:
            FixProgramVnum (vnum, 
                            pRenumberStuff->pOldObjVnum, 
                            pRenumberStuff->pNewObjVnum, 
                            pRenumberStuff->iObjCount);
            break;
        } // end of switch
      } // end of having a vnum and wanting to renumber
    } // end of vnum

  if (fread_condition_word () != ')')
    ThrowErrorException ("no \")\" symbol");

  if (pCondition->bBoolean)
    {
    pWord = pWordOrig;    // put word pointer back
    return;   // boolean condition - no arguments
    }

  CString strOperator = fread_condition_operator ();

  if (strOperator == "")
    ThrowErrorException ("expected operator (eg. ==)");

  if (strOperator != "==" &&
      strOperator != "!=" &&
      strOperator != "/" &&
      strOperator != "!/" &&
      strOperator != "<" &&
      strOperator != ">" &&
      strOperator != "<=" &&
      strOperator != ">=" &&
      strOperator != "&" &&
      strOperator != "|")
    ThrowErrorException ("improper operator \"%s\"",
                          (LPCTSTR) strOperator);

      
      
  // get what we are comparing it to

CString strValue = fread_condition_word ();

  vnum = atoi (strValue);

  if (vnum && pRenumberStuff)
    {
    switch (pCondition->Value)
      {
      case eCondMobile:
          FixProgramVnum (vnum, 
                          pRenumberStuff->pOldMobVnum, 
                          pRenumberStuff->pNewMobVnum, 
                          pRenumberStuff->iMobCount);
          break;
      case eCondRoom:
          FixProgramVnum (vnum, 
                          pRenumberStuff->pOldRoomVnum, 
                          pRenumberStuff->pNewRoomVnum, 
                          pRenumberStuff->iRoomCount);
          break;
      case eCondObject:
          FixProgramVnum (vnum, 
                          pRenumberStuff->pOldObjVnum, 
                          pRenumberStuff->pNewObjVnum, 
                          pRenumberStuff->iObjCount);
          break;
      } // end of switch
    } // end of having a vnum and wanting to renumber

  pWord = pWordOrig;    // put word pointer back

  }   // end of HandleCondition

bool CheckCommandSyntax (CString & strCommands, 
                         CString & strMessage,            // error message on error
                         CIntList & xref_rooms,
                         CIntList & xref_objects,
                         CIntList & xref_mobs,
                         t_prenumber pRenumberStuff)
  {
CString str;    // this is for each line of the commands
CString strCommand;
CString strArgument;
CCommand * command;
CSocial * social;
CSkill * skill;
CIntList iflist;

bool bError = false;
POSITION pos;

   strMessage.Empty ();
   xref_rooms.RemoveAll ();
   xref_objects.RemoveAll ();
   xref_mobs.RemoveAll ();

   // don't do it if we have not commands, socials or skills
   if (App.m_CommandList.IsEmpty () 
    || App.m_SocialList.IsEmpty ()
    || App.m_SkillList.IsEmpty ())
     return false;

CStringList lControlFlow;

   lControlFlow.AddTail ("if");
   lControlFlow.AddTail ("or");
   lControlFlow.AddTail ("and");
   lControlFlow.AddTail ("endif");
   lControlFlow.AddTail ("break");
   lControlFlow.AddTail ("else");

   lDirections.RemoveAll ();
   lDirections.AddTail ("n");
   lDirections.AddTail ("s");
   lDirections.AddTail ("e");
   lDirections.AddTail ("w");
   lDirections.AddTail ("ne");
   lDirections.AddTail ("nw");
   lDirections.AddTail ("se");
   lDirections.AddTail ("sw");
   lDirections.AddTail ("u");
   lDirections.AddTail ("d");
   lDirections.AddTail ("somewhere");

   pWord = strCommands.GetBuffer (strCommands.GetLength () + GROWSIZE);
   nLineNumber = 1;     // start at line 1
   nGrownAmount = 0;    // amount we have grown the buffer so far

   try
     {

     while (!bError && *pWord)
       {

       // get next word
       
       strCommand = fread_word (false); // commands are not quoted, but might be a quote

       if (strCommand.IsEmpty ())
         {
         fread_to_eol ();
         continue;   // ignore blank commands
         }

       strCommand.MakeLower ();

       // if found in "control flow" list, keep going (eg. if, else)

       if (lControlFlow.Find (strCommand))
         {
          if (strCommand == "if")
            {
            iflist.AddTail (0);   // add if to list, no elses yet
            HandleCondition (pRenumberStuff);   // look for vnums in conditions
            }
          else if (strCommand == "endif")
            {
            if (iflist.IsEmpty ())
              ThrowErrorException ("ENDIF without corresponding IF");
            iflist.RemoveTail ();   // one less else now
            } // end of endif
          else if (strCommand == "or")
            {
            if (iflist.IsEmpty ())
              ThrowErrorException ("OR without corresponding IF");
            HandleCondition (pRenumberStuff);   // look for vnums in conditions
            } // end of OR
          else if (strCommand == "and")
            {
            if (iflist.IsEmpty ())
              ThrowErrorException ("AND without corresponding IF");
            HandleCondition (pRenumberStuff);   // look for vnums in conditions
            } // end of AND
          else if (strCommand == "else")
            {
            if (iflist.IsEmpty ())
              ThrowErrorException ("ELSE without corresponding IF");

            int iHaveElse = 1;
            POSITION matchpos = NULL;
            // Scan backwards to find last if without an else, and associate
            // the else with it. This is so we can have lots of ifs and elses
            // and know which else belongs to which if
            for (pos = iflist.GetTailPosition (); pos; )
              {
              matchpos = pos;
              if ((iHaveElse = iflist.GetPrev (pos)) == 0)
                break;
              }

            if (iHaveElse)
              ThrowErrorException ("Too many ELSES");

            iflist.SetAt (matchpos, 1); // note else here
            } // end of else

         fread_to_eol ();
         continue;
         }  // end of flow control command (if, endif etc.)

       // look up command in table

      for (pos = App.m_CommandList.GetHeadPosition (); pos; )
        {
        command =  App.m_CommandList.GetNext (pos);

        if (!str_prefix (strCommand, command->name))
          { // command found!
          int vnum;

          command->iReferenced++;   // note used count

          // read vnum or argument if necessary

          if (command->FollowedBy & eCmdName)
            {
            fread_word ();
            }   // end of having an name after it

          if (command->FollowedBy & eCmdSpell)
            {
            CString strSpell = fread_word ();
            for (POSITION skillpos = App.m_SkillList.GetHeadPosition (); skillpos; )
              {
              skill =  App.m_SkillList.GetNext (skillpos);

              if (skill->type == SKILL_SPELL && !str_prefix (strSpell, skill->name))
                {
                skill->iReferenced++;   // note used count
                break;
                }   // end of match on spell
              }   // end of checking each skill
             
             if (!skillpos)
               ThrowErrorException ("Unknown spell \"%s\"", (LPCTSTR) strSpell);

            }   // end of having an spell after it

          if (command->FollowedBy & eCmdObject)
            {
            vnum = atoi (fread_word ());   // may not be a number, eg $n!
            if (vnum)
              {
              if (xref_objects.Find (vnum) == NULL)
                xref_objects.AddTail (vnum);      // another object cross reference
              if (pRenumberStuff)
                FixProgramVnum (vnum, 
                                pRenumberStuff->pOldObjVnum, 
                                pRenumberStuff->pNewObjVnum, 
                                pRenumberStuff->iObjCount);
              }
            }   // end of having an object after it


          if (command->FollowedBy & eCmdRoom)
            {
            vnum = atoi (fread_word ());   // may not be a number, eg $n!
            if (vnum)
              if (xref_rooms.Find (vnum) == NULL)
                xref_rooms.AddTail (vnum);      // another room cross reference
              if (pRenumberStuff)
                FixProgramVnum (vnum, 
                                pRenumberStuff->pOldRoomVnum, 
                                pRenumberStuff->pNewRoomVnum, 
                                pRenumberStuff->iRoomCount);
            }   // end of having a room after it

          if (command->FollowedBy & eCmdMobile)
            {
            vnum = atoi (fread_word ());   // may not be a number, eg $n!
            if (vnum)
              if (xref_mobs.Find (vnum) == NULL)
                xref_mobs.AddTail (vnum);      // another mob cross reference
              if (pRenumberStuff)
                FixProgramVnum (vnum, 
                                pRenumberStuff->pOldMobVnum, 
                                pRenumberStuff->pNewMobVnum, 
                                pRenumberStuff->iMobCount);
            }   // end of having a mob after it


          if (!(command->FollowedBy & eCmdRepeat))
            fread_to_eol ();

          break;    // out of command-lookup loop
          }   // end of command found

        }   // command-lookup loop

       // if pos is zero, we didn't find the command

       // so, look up social in table

      if (!pos)
        for (pos = App.m_SocialList.GetHeadPosition (); pos; )
          {
          social =  App.m_SocialList.GetNext (pos);

          if (!str_prefix (strCommand, social->name))
            {
            social->iReferenced++;   // note used count
            fread_to_eol ();   // socials are not followed by commands
            break;
            }

          }

       // if pos is zero, we didn't find the command or social

       // so, look up skill in table

      if (!pos)
        for (pos = App.m_SkillList.GetHeadPosition (); pos; )
          {
          skill =  App.m_SkillList.GetNext (pos);

          if (skill->type == SKILL_SKILL && !str_prefix (strCommand, skill->name))
            {
            skill->iReferenced++;   // note used count
            fread_to_eol ();   // skills are not followed by commands
            break;
            }

          }

       // if pos is zero, we didn't find the command, social or skill

       if (!pos)
         ThrowErrorException ("Unknown MUD program command \"%s\"", (LPCTSTR) strCommand);

       }    // end of reading each line

// if we don't have an error yet, report unmatched if/endif

    if (!bError)
      if (!iflist.IsEmpty ())
        {
        int i = iflist.GetCount ();
         strMessage.Format ("There %s %i IF%s without %scorresponding ENDIF%s",
                            i == 1 ? "is" : "are",
                            PLURAL (i),
                            i == 1 ? "a " : " ",
                            i == 1 ? "" : "s");
         bError = true;
        }

    }   // end of try block
  catch (CException * e)
    {

    TCHAR    szCause[255];

    e->GetErrorMessage(szCause, 255);
    strMessage.Format ("%s at line %i of program",
                       (LPCTSTR) szCause, nLineNumber);
    bError = true;
    e->Delete ();
    }

  strCommands.ReleaseBuffer ();  

  return bError;    // return true on syntax error
  } // end of CheckCommandSyntax