long CMUSHclientDoc::SetTriggerOption(LPCTSTR TriggerName, LPCTSTR OptionName, LPCTSTR Value) 
{
CString strTriggerName = TriggerName;
CString strValue = Value;
CTrigger * trigger_item;

  // trim spaces from name, make lower-case
  CheckObjectName (strTriggerName, false);

  if (!GetTriggerMap ().Lookup (strTriggerName, trigger_item))
    return eTriggerNotFound;

CString strOptionName = OptionName;

  strOptionName.MakeLower();
  strOptionName.TrimLeft ();
  strOptionName.TrimRight ();

int iItem;
int iResult = FindBaseOption (strOptionName, TriggerOptionsTable, iItem);

bool bChanged;

  if (iResult == eOK)
    {
    // this is a numeric option
    
    // for boolean options, accept "y" or "n"
    if (TriggerOptionsTable [iItem].iMinimum == 0 &&
      TriggerOptionsTable [iItem].iMaximum == 0)
      {
      if (strValue == "Y" || strValue == "y")
        Value = "1";
      else if (strValue == "N" || strValue == "n")
        Value = "0";
      }

    if (!IsNumber (Value, true))
       return eOptionOutOfRange;

    long iValue = atol (Value);

    if (m_CurrentPlugin &&
        (TriggerOptionsTable [iItem].iFlags & OPT_PLUGIN_CANNOT_WRITE))
    	return ePluginCannotSetOption;  // not available to plugin

    if (TriggerOptionsTable [iItem].iFlags & OPT_CANNOT_WRITE)
    	return ePluginCannotSetOption;  // not available for writing at all    

    iResult = SetBaseOptionItem (iItem,
                        TriggerOptionsTable,
                        NUMITEMS (TriggerOptionsTable),
                        (char *) trigger_item, 
                        iValue,
                        bChanged);

    if (bChanged)
      {
      if (!m_CurrentPlugin) // plugin mods don't really count
        SetModifiedFlag (TRUE);   // document has changed
      trigger_item->nUpdateNumber    = App.GetUniqueNumber ();   // for concurrency checks
      }

    if (strOptionName == "sequence")
      SortTriggers ();

    return iResult;

    }  // end of found
  else
    { // not numeric option, try alpha
    int iResult = FindBaseAlphaOption (strOptionName, TriggerAlphaOptionsTable, iItem);
    if (iResult == eOK)
      {

      // alpha option

      if (m_CurrentPlugin &&
          (TriggerAlphaOptionsTable [iItem].iFlags & OPT_PLUGIN_CANNOT_WRITE))
    	  return ePluginCannotSetOption;  // not available to plugin

      if (TriggerAlphaOptionsTable [iItem].iFlags & OPT_CANNOT_WRITE)
    	  return ePluginCannotSetOption;  // not available for writing at all    

      // ------ preliminary validation before setting the option

      // cannot have null match text
      if (strOptionName == "match" || 
          strOptionName == "ignore_case" ||
          strOptionName == "multi_line")
        {
        if (strValue.IsEmpty ())
          return eTriggerCannotBeEmpty;

        t_regexp * regexp = NULL;

        CString strRegexp; 

        if (trigger_item->bRegexp)
          strRegexp = strValue;
        else
          strRegexp = ConvertToRegularExpression (strValue);

        // compile regular expression
        try 
          {
          regexp = regcomp (strRegexp, (trigger_item->ignore_case ? PCRE_CASELESS : 0) |
                                       (trigger_item->bMultiLine  ? PCRE_MULTILINE : 0) |
                                       (m_bUTF_8 ? PCRE_UTF8 : 0)
                                       );
          }   // end of try
        catch(CException* e)
          {
          e->Delete ();
          return eBadRegularExpression;
          } // end of catch
      
        delete trigger_item->regexp;    // get rid of old one
        trigger_item->regexp = regexp;

        } // end of option "match"  
      else if (strOptionName == "script")
        {
  
        // get trigger dispatch ID

        if (GetScriptEngine () && !strValue.IsEmpty ())
          {
          DISPID dispid = DISPID_UNKNOWN;
          CString strMessage;
          dispid = GetProcedureDispid (strValue, "trigger", TriggerName, strMessage);
          if (dispid == DISPID_UNKNOWN)
            return eScriptNameNotLocated;
          trigger_item->dispid  = dispid;   // update dispatch ID
          }
        } // end of option "script"


      // set the option now

      iResult = SetBaseAlphaOptionItem (iItem,
                        TriggerAlphaOptionsTable,
                        NUMITEMS (TriggerAlphaOptionsTable),
                        (char *) trigger_item,  
                        strValue,
                        bChanged);

      if (bChanged)
        {
        if (!m_CurrentPlugin) // plugin mods don't really count
          SetModifiedFlag (TRUE);   // document has changed
        trigger_item->nUpdateNumber    = App.GetUniqueNumber ();   // for concurrency checks
        }

      return iResult;
      }  // end of found alpha option
    }  // end of not numeric option

 return eUnknownOption;
}   // end of SetTriggerOption
Exemple #2
0
// general MXP error message routine - appends a newline, and also writes to debug 
void CMUSHclientDoc::MXP_error (const int iLevel, 
                                const long iMessageNumber, 
                                CString strMessage)
  {

char * sLevel [] = 
  {
  " ",
  "E", // error
  "W", // warning
  "I", // info
  "A", // all
  };

char * p = "?";

  // turn error level into a character
  if (iLevel >= 0 && iLevel < NUMITEMS (sLevel))
    p = sLevel [iLevel];

  if (iLevel == DBG_ERROR)
    {
    m_iMXPerrors++;
    MXP_Restore_Mode (); // an error cancels secure-once mode
    } // end of error

  // call script if required
  if (m_dispidOnMXP_Error != DISPID_UNKNOWN)
    {
    long nInvocationCount = 0;

    CString strType = "MXP Error";
    CString strReason =  "processing MXP error";

    if (GetScriptEngine () && GetScriptEngine ()->IsLua ())
      {
      list<double> nparams;
      list<string> sparams;
      nparams.push_back (iMessageNumber);  // error number
      nparams.push_back (m_LineList.GetCount ());  // line
      sparams.push_back (p);    // level (a character)
      sparams.push_back ((LPCTSTR) strMessage);    // message
      bool result;
      GetScriptEngine ()->ExecuteLua (m_dispidOnMXP_Error, 
                                     m_strOnMXP_Error, 
                                     eWorldAction,
                                     strType, 
                                     strReason, 
                                     nparams,
                                     sparams, 
                                     nInvocationCount,
                                     NULL, NULL, NULL,
                                     &result);
      if (result)
         return;
      }   // end of Lua
   else
       {
      // WARNING - arguments should appear in REVERSE order to what the sub expects them!

      enum
        {
        eErrorMessage,
        eLineNumber,
        eMessageNumber,
        eErrorLevel,
        eArgCount,     // this MUST be last
        };    

      COleVariant args [eArgCount];
      DISPPARAMS params = { args, NULL, eArgCount, 0 };

      args [eErrorLevel] = p;
      args [eMessageNumber] = iMessageNumber;
      args [eLineNumber] = (long) m_LineList.GetCount ();
      args [eErrorMessage] = strMessage;

      COleVariant result;

      ExecuteScript (m_dispidOnMXP_Error,  
                     m_strOnMXP_Error,
                     eWorldAction,
                     strType, 
                     strReason,
                     params, 
                     nInvocationCount,
                     &result); 

      // if the function returns a non-zero result, don't display the message
      if (result.vt != VT_EMPTY)
        {
        result.ChangeType (VT_I4);  // make a long
        if (result.vt == VT_I4)   // conversion successful
          if (result.lVal)        // return if non-zero
            return;
        }
      }  // not Lua
    }  // end of script callback wanted


  // tell each plugin about the error tag
  if (m_bPluginProcessesError)
    SendToAllPluginCallbacks (ON_PLUGIN_MXP_ERROR, 
                              CFormat ("%s,%ld,%ld,%s",
                              (LPCTSTR) p,
                              iMessageNumber,
                              (long) m_LineList.GetCount (),
                              (LPCTSTR) strMessage));


  CString strTitle = MXP_ERROR_WINDOW;
  strTitle += " - ";
  strTitle += m_mush_name;

  if (iLevel > m_iMXPdebugLevel)   // only show required level
    return;

  CString str = CFormat ("%s %5i: (%5i) %s%s",
                      p,     // error level
                      iMessageNumber,   // actual error number
                      m_LineList.GetCount (),  // which line
                      (LPCTSTR) strMessage,     // what message
                      ENDLINE);

  AppendToTheNotepad (strTitle, 
                      str,                 // start new line
                      false,   // append
                      eNotepadMXPdebug);
  }
Exemple #3
0
bool CMUSHclientDoc::MXP_StartTagScript  (const CString & strName, 
                     const CString & strArguments,
                     CArgumentList & ArgumentList)
  {

  // don't make it too easy to dummy up AFK replies
  if (strName == "afk")
    return false;

  if (!SendToAllPluginCallbacks (ON_PLUGIN_MXP_OPENTAG, 
                                CFormat ("%s,%s",
                                (LPCTSTR) strName,
                                (LPCTSTR) strArguments)
                                ), true)
      return true;    


  // see if main script wants to do anything
  if (m_dispidOnMXP_OpenTag == DISPID_UNKNOWN)
    return false;

  long nInvocationCount = 0;
  long iCount = ArgumentList.GetCount ();

  CString strType = "MXP open tag";
  CString strReason =  TFormat ("opening MXP tag %s", (LPCTSTR) strName);

  if (GetScriptEngine () && GetScriptEngine ()->IsLua ())
    {
    list<double> nparams;
    list<string> sparams;
    sparams.push_back ((LPCTSTR) strName);    // name of tag
    sparams.push_back ((LPCTSTR) strArguments);  // all arguments

    map <string, string> table;

    CArgument * pArgument;
    POSITION pos;

    // put the arguments into the table

    for (iCount = 0, pos = ArgumentList.GetHeadPosition (); pos; iCount++)
      {
      pArgument = ArgumentList.GetNext (pos);
      CString strName = pArgument->strName;

      // empty ones we will put there by position
      if (strName.IsEmpty ())
        strName = CFormat ("%i",
                      pArgument->iPosition);
      
      table [(LPCTSTR) strName] = pArgument->strValue;
      }      // end of looping through each argument

    bool result;
    GetScriptEngine ()->ExecuteLua (m_dispidOnMXP_OpenTag, 
                                   m_strOnMXP_OpenTag, 
                                   eWorldAction,
                                   strType, 
                                   strReason, 
                                   nparams,
                                   sparams, 
                                   nInvocationCount,
                                   NULL,
                                   &table,
                                   NULL,
                                   &result);
    return result;
    }   // end of Lua

  COleSafeArray sa;   // for wildcard list

  if (iCount) // cannot create empty array dimension
    {
    sa.CreateOneDim (VT_VARIANT, iCount);

    CArgument * pArgument;
    POSITION pos;

    // put the arguments into the array

    for (iCount = 0, pos = ArgumentList.GetHeadPosition (); pos; iCount++)
      {
      pArgument = ArgumentList.GetNext (pos);

      // the array must be a bloody array of variants, or VBscript kicks up
      COleVariant v;
      
      // empty ones we will put there by position
      if (pArgument->strName.IsEmpty ())
        v = CFormat ("%i=%s",
                      pArgument->iPosition,
                      (LPCTSTR) pArgument->strValue);
      else
        v = CFormat ("%s=%s",
                      (LPCTSTR) pArgument->strName,
                      (LPCTSTR) pArgument->strValue);
      sa.PutElement (&iCount, &v);
      }      // end of looping through each argument
    } // end of having at least one

  // WARNING - arguments should appear in REVERSE order to what the sub expects them!

  enum
    {
    eArgumentArray,
    eArguments,
    eTagName,
    eArgCount,     // this MUST be last
    };    

  COleVariant args [eArgCount];
  DISPPARAMS params = { args, NULL, eArgCount, 0 };

  args [eTagName] = strName;
  args [eArguments] = strArguments;
  args [eArgumentArray] = sa;

  COleVariant result;

  ExecuteScript (m_dispidOnMXP_OpenTag,  
                 m_strOnMXP_OpenTag,
                 eWorldAction,
                 strType, 
                 strReason,
                 params, 
                 nInvocationCount,
                 &result); 

  // if the function returns a non-zero result, don't go ahead
  if (result.vt != VT_EMPTY)
    {
    result.ChangeType (VT_I4);  // make a long
    if (result.vt == VT_I4)   // conversion successful
      if (result.lVal)        // return if non-zero
        return true;
    }

  return false;
  } // end of CMUSHclientDoc::MXP_StartTagScript 
long CMUSHclientDoc::AddTriggerEx(LPCTSTR TriggerName, 
                                  LPCTSTR MatchText, 
                                  LPCTSTR ResponseText, 
                                  long Flags, 
                                  short Colour, 
                                  short Wildcard, 
                                  LPCTSTR SoundFileName, 
                                  LPCTSTR ScriptName, 
                                  short SendTo, 
                                  short Sequence) 
{
CString strTriggerName = TriggerName;
CTrigger * trigger_item;
DISPID dispid = DISPID_UNKNOWN;
long nStatus;
bool bReplace = false;

  // allow blank names, assign one :)
  if (strTriggerName.IsEmpty ())
    strTriggerName.Format ("*trigger%s", (LPCTSTR) App.GetUniqueString ());
  else
    // return if bad name
    if (nStatus = CheckObjectName (strTriggerName))
      return nStatus;

  // if it already exists, error
  if (GetTriggerMap ().Lookup (strTriggerName, trigger_item))
    if (Flags & eReplace)
      bReplace = true;
    else
      return eTriggerAlreadyExists;

  // cannot have null match text
  if (strlen (MatchText) == 0)
    return eTriggerCannotBeEmpty;

  // check sequence

  if (Sequence < 0 || Sequence > 10000)
    return eTriggerSequenceOutOfRange;

  // check send to
  if (SendTo < 0 || SendTo >= eSendToLast) 
    return eTriggerSendToInvalid; 

  // must have a label for 'send to label'
  if (SendTo == eSendToVariable)
    if (CheckObjectName (strTriggerName))
       return eTriggerLabelNotSpecified;

// get trigger dispatch ID
  
  if (GetScriptEngine () && strlen (ScriptName) != 0)
    {
    CString strMessage;
    dispid = GetProcedureDispid (ScriptName, "trigger", TriggerName, strMessage);
    if (dispid == DISPID_UNKNOWN)
      return eScriptNameNotLocated;
    }

  t_regexp * regexp = NULL;

  CString strRegexp; 

  if (Flags & eTriggerRegularExpression)
    strRegexp = MatchText;
  else
    strRegexp = ConvertToRegularExpression (MatchText);

  // compile regular expression
  try 
    {
    regexp = regcomp (strRegexp, (Flags & eIgnoreCase ? PCRE_CASELESS : 0) | (m_bUTF_8 ? PCRE_UTF8 : 0));
    }   // end of try
  catch(CException* e)
    {
    e->Delete ();
    return eBadRegularExpression;
    } // end of catch

  // trigger replacement wanted
  if (bReplace)
    {
    // the trigger seems to exist - delete its pointer
    delete trigger_item;

    // now delete its entry
    GetTriggerMap ().RemoveKey (strTriggerName);
    }

  // create new trigger item and insert in trigger map
  GetTriggerMap ().SetAt (strTriggerName, trigger_item = new CTrigger);

  if ((Flags & eTemporary) == 0)
    if (!m_CurrentPlugin) // plugin mods don't really count
      SetModifiedFlag (TRUE);

  trigger_item->nUpdateNumber    = App.GetUniqueNumber ();   // for concurrency checks
  trigger_item->strInternalName  = strTriggerName;    // for deleting one-shot triggers

  trigger_item->trigger          = MatchText;
  trigger_item->contents         = ResponseText;
  trigger_item->colour           = Colour;
  trigger_item->ignore_case      = (Flags & eIgnoreCase) != 0;
  trigger_item->bOmitFromOutput  = (Flags & eOmitFromOutput) != 0;
  trigger_item->bKeepEvaluating  = (Flags & eKeepEvaluating) != 0;
  trigger_item->omit_from_log    = (Flags & eOmitFromLog) != 0;    
  trigger_item->bEnabled         = (Flags & eEnabled) != 0;
  trigger_item->bRegexp          = (Flags & eTriggerRegularExpression) != 0;
  trigger_item->bExpandVariables = (Flags & eExpandVariables) != 0;
  trigger_item->bTemporary       = (Flags & eTemporary) != 0;
  trigger_item->bLowercaseWildcard       = (Flags & eLowercaseWildcard) != 0;
  trigger_item->bOneShot         = (Flags & eTriggerOneShot) != 0;
  trigger_item->strProcedure     = ScriptName;
  trigger_item->strLabel         = TriggerName;
  trigger_item->iClipboardArg    = Wildcard;
  trigger_item->sound_to_play    = SoundFileName;
  trigger_item->dispid           = dispid;
  trigger_item->regexp           = regexp;
  trigger_item->iSendTo          = SendTo;
  trigger_item->iSequence        = Sequence;
  trigger_item->strVariable      = TriggerName;   // kludge


  if (Colour < 0 || Colour >= MAX_CUSTOM)
    trigger_item->colour = SAMECOLOUR;
  if (Wildcard < 0 || Wildcard > 10)
    trigger_item->iClipboardArg = 0;

  SortTriggers ();

	return eOK;
}     // end of CMUSHclientDoc::AddTriggerEx
Exemple #5
0
void CMUSHclientDoc::CheckTimerList (CTimerMap & TimerMap)
  {
CTimer * timer_item;
CString strTimerName;
CmcDateTime tNow = CmcDateTime::GetTimeNow();
CmcDateTimeSpan tsOneDay (1, 0, 0, 0);

// check for deleted chat sessions

  for (POSITION chatpos = m_ChatList.GetHeadPosition (); chatpos; )
    {
    POSITION oldpos = chatpos;
    CChatSocket * pSocket = m_ChatList.GetNext (chatpos);
    if (pSocket->m_bDeleteMe)
      {
      m_ChatList.RemoveAt (oldpos);
      delete pSocket;
      break;    // list is no longer valid
      }
    }

  set <string> firedTimersList;
  POSITION pos;

// iterate through all timers for this document - first build list of them

  for (pos = TimerMap.GetStartPosition(); pos; )
    {


    TimerMap.GetNextAssoc (pos, strTimerName, timer_item);

    if (!timer_item->bEnabled)    // ignore un-enabled timers
      continue;

    // no timer activity whilst closed or in the middle of connecting, or if not enabled

    if (!timer_item->bActiveWhenClosed)
      if (m_iConnectPhase != eConnectConnectedToMud)
        continue;

    // if not ready to fire yet, ignore it

    if (timer_item->tFireTime > tNow)
      continue;

    firedTimersList.insert ((LPCTSTR) strTimerName);       // add to list of fired timers
    }


  // now process list, checking timer still exists in case a script deleted one
  // see: http://www.gammon.com.au/forum/?id=10358

  for (set <string>::iterator it = firedTimersList.begin ();
       it != firedTimersList.end ();
       it++)
    {
    // get next fired timer from list
    strTimerName = it->c_str ();

    // check still exists, get pointer if so
    if (!TimerMap.Lookup (strTimerName, timer_item))
      continue;

    timer_item->nMatched++;   // count timer matches
    timer_item->tWhenFired = tNow;  // when it fired

    m_iTimersFiredCount++;
    m_iTimersFiredThisSessionCount++;

//    TRACE1 ("Fired at = %10.8f\n", timer_item->tWhenFired.m_dt);

    if (timer_item->strLabel.IsEmpty ())
      Trace ("Fired unlabelled timer ");
    else
      Trace ("Fired timer %s", (LPCTSTR) timer_item->strLabel);

//    TRACE1 ("Fire time = %10.8f\n", timer_item->tFireTime.m_dt);

// update fire time - before calling the script, in case it takes a long time

    if (timer_item->iType == CTimer::eAtTime)
      timer_item->tFireTime += tsOneDay;
    else
      timer_item->tFireTime += CmcDateTimeSpan (0,    // add the interval
                                          timer_item->iEveryHour, 
                                          timer_item->iEveryMinute, 
                                          timer_item->fEverySecond);

    // in case clock changes or some such thing, make sure timer will be due to
    // fire in the future, not the past, or it might go mad and keep firing

    if (timer_item->tFireTime <= tNow)
      ResetOneTimer (timer_item);

    // if one-shot, disable it, so if the timer routine finds it again while
    // it is still executing (eg. due to a syntax error dialog box) then
    // it won't fire again.

    if (timer_item->bOneShot)
      timer_item->bEnabled = false;


// send timer message, if this timer list is "active"

    CString strExtraOutput;

    timer_item->bExecutingScript = true;     // cannot be deleted now
    m_iCurrentActionSource = eTimerFired;
    SendTo (timer_item->iSendTo, 
            timer_item->strContents, 
            timer_item->bOmitFromOutput, // omit from output
            timer_item->bOmitFromLog,    // omit from log
            TFormat ("Timer: %s", (LPCTSTR) timer_item->strLabel),
            timer_item->strVariable,
            strExtraOutput
            );
    m_iCurrentActionSource = eUnknownActionSource;
    timer_item->bExecutingScript = false;     // can be deleted now

    // display any stuff sent to output window

    if (!strExtraOutput.IsEmpty ())
       DisplayMsg (strExtraOutput, strExtraOutput.GetLength (), COMMENT);

// invoke script subroutine, if any

    if (!timer_item->strProcedure.IsEmpty ())
      if (CheckScriptingAvailable ("Timer", timer_item->dispid, timer_item->strProcedure))
         continue;

    if (timer_item->dispid != DISPID_UNKNOWN)        // if we have a dispatch id
      {
      
      CString strType = "timer";
      CString strReason =  TFormat ("processing timer \"%s\"", 
                                    (LPCTSTR) timer_item->strLabel);

      // get unlabelled timer's internal name
      const char * pLabel = timer_item->strLabel;
      if (pLabel [0] == 0)
        pLabel = GetTimerRevMap () [timer_item].c_str ();

      if (GetScriptEngine () && GetScriptEngine ()->IsLua ())
        {
        list<double> nparams;
        list<string> sparams;
        sparams.push_back (pLabel);
        timer_item->bExecutingScript = true;     // cannot be deleted now
        GetScriptEngine ()->ExecuteLua (timer_item->dispid, 
                                       timer_item->strProcedure, 
                                       eTimerFired,
                                       strType, 
                                       strReason,
                                       nparams,
                                       sparams, 
                                       timer_item->nInvocationCount);
        timer_item->bExecutingScript = false;     // can be deleted now
        }   // end of Lua
      else
        {
        // prepare for the arguments (so far, 1 which is the timer name)
  
        // WARNING - arguments should appear in REVERSE order to what the sub expects them!

        enum
          {
          eTimerName,
          eArgCount,     // this MUST be last
          };    

        COleVariant args [eArgCount];
        DISPPARAMS params = { args, NULL, eArgCount, 0 };

  //      args [eTimerName] = strTimerName;
        args [eTimerName] = pLabel;
        timer_item->bExecutingScript = true;     // cannot be deleted now
        ExecuteScript (timer_item->dispid,  
                       timer_item->strProcedure,
                       eTimerFired,
                       strType, 
                       strReason,
                       params, 
                       timer_item->nInvocationCount);
        timer_item->bExecutingScript = false;     // can be deleted now

        } // not Lua
      }     // end of having a dispatch ID


    // If they passed the wrong arguments to the timer routine, the dialog box
    // might appear, and the timer be deleted, before we get a chance to
    // do this code, in which case the timer has gone.
    // Just get it again to be sure ...  [#430]

    if (!TimerMap.Lookup (strTimerName, timer_item))
      return;

// if one-shot timer, delete from list

    if (timer_item->bOneShot)
      {
      TimerMap.RemoveKey (strTimerName);
      delete timer_item;
      SortTimers ();
      }
    }   // end of processing each timer

  } // end of CMUSHclientDoc::CheckTimerMap
Exemple #6
0
bool CMUSHclientDoc::ExecuteAliasScript (CAlias * alias_item,
                             const CString strCurrentLine)
  {

  if (CheckScriptingAvailable ("Alias", alias_item->dispid, alias_item->strProcedure))
     return false;

  if (alias_item->dispid != DISPID_UNKNOWN)        // if we have a dispatch id
    {

    CString strType = "alias";
    CString strReason =  TFormat ("processing alias \"%s\"", 
                                  (LPCTSTR) alias_item->strLabel);

    // get unlabelled alias's internal name
    const char * pLabel = alias_item->strLabel;
    if (pLabel [0] == 0)
       pLabel = GetAliasRevMap () [alias_item].c_str ();

    if (GetScriptEngine () && GetScriptEngine ()->IsLua ())
      {
      list<double> nparams;
      list<string> sparams;
      sparams.push_back (pLabel);
      sparams.push_back ((LPCTSTR) strCurrentLine);
      alias_item->bExecutingScript = true;     // cannot be deleted now
      GetScriptEngine ()->ExecuteLua (alias_item->dispid, 
                                     alias_item->strProcedure, 
                                     eDontChangeAction,
                                     strType, 
                                     strReason, 
                                     nparams,
                                     sparams, 
                                     alias_item->nInvocationCount,
                                     alias_item->regexp); 
      alias_item->bExecutingScript = false;     // can be deleted now
      return true;
      }   // end of Lua

    // prepare for the arguments, so far, 3 which are: 
    //  1. alias name, 
    //  2. expanded line 
    //  3. replacement string

    // WARNING - arguments should appear in REVERSE order to what the sub expects them!

    enum
      {
      eWildcards,
      eInputLine,
      eAliasName,
      eArgCount,     // this MUST be last
      };    

    COleSafeArray sa;   // for wildcard list
    COleVariant args [eArgCount];
    DISPPARAMS params = { args, NULL, eArgCount, 0 };

    args [eAliasName] = pLabel;
    args [eInputLine] = strCurrentLine;

    // --------------- set up wildcards array ---------------------------
    sa.Clear ();
    // nb - to be consistent with %1, %2 etc. we will make array 1-relative
    sa.CreateOneDim (VT_VARIANT, MAX_WILDCARDS, NULL, 1);
    long i;
    for (i = 1; i < MAX_WILDCARDS; i++)
      {
      COleVariant v (alias_item->wildcards [i].c_str ());
      sa.PutElement (&i, &v);
      }
    // i should be MAX_WILDCARDS (10) now ;)
    COleVariant v (alias_item->wildcards [0].c_str ()); // the whole matching line
    sa.PutElement (&i, &v);
    args [eWildcards] = sa;
    
    alias_item->bExecutingScript = true;     // cannot be deleted now
    ExecuteScript (alias_item->dispid,  
                   alias_item->strProcedure,
                   eDontChangeAction,     // don't change current action
                   strType, 
                   strReason,
                   params, 
                   alias_item->nInvocationCount); 
    alias_item->bExecutingScript = false;     // can be deleted now

    return true;
    }     // end of having a dispatch ID

  return false;
  } // end of CMUSHclientDoc::ExecuteAliasScript
void CMUSHclientDoc::Help(LPCTSTR Name) 
{
map<string, string> lua_specials;

// special Lua help
  lua_specials ["lua"]   = "lua";
  lua_specials ["lua b"] = "lua_base";
  lua_specials ["lua c"] = "lua_coroutines";
  lua_specials ["lua d"] = "lua_debug";
  lua_specials ["lua i"] = "lua_io";
  lua_specials ["lua m"] = "lua_math";
  lua_specials ["lua o"] = "lua_os";
  lua_specials ["lua p"] = "lua_package";
  lua_specials ["lua r"] = "lua_rex";
  lua_specials ["lua s"] = "lua_string";
  lua_specials ["lua t"] = "lua_tables";
  lua_specials ["lua u"] = "lua_utils";

CString m_strFilter = Name;

  m_strFilter.MakeLower ();
  m_strFilter.TrimLeft ();
  m_strFilter.TrimRight ();

  // a special case - b is used twice
  if (m_strFilter == "lua bc")
    {
    ShowHelp ("DOC_", "lua_bc"); 
    return;
    }

  CString strFunction;

  string sFirst5 = m_strFilter.Left (5);

  map<string, string>::const_iterator it = lua_specials.find (sFirst5);

  if (it != lua_specials.end ())
    {
    ShowHelp ("DOC_", it->second.c_str ()); 
    return;
    }
  else if (!m_strFilter.IsEmpty ())
    {
    // first find direct match on a Lua function
    if (LuaFunctionsSet.find ((LPCTSTR) m_strFilter) != LuaFunctionsSet.end ())
      {
      ShowHelp ("LUA_", m_strFilter);   
      return;
      }

    // then try a world function
    for (int i = 0; InternalFunctionsTable [i].sFunction [0]; i++)
      {
      strFunction = InternalFunctionsTable [i].sFunction;
      strFunction.MakeLower ();

      if (strFunction == m_strFilter)
        {
        ShowHelp ("FNC_", InternalFunctionsTable [i].sFunction);   // back to proper capitalization
        return;
        }
      }
    } // end of non-empty name

  bool bLua = false;
  
  if (GetScriptEngine () && GetScriptEngine ()->L)
    bLua = true;

  // not exact match, show list matching filter
 ShowFunctionslist (m_strFilter, bLua);
}   // end of CMUSHclientDoc::Help
long CMUSHclientDoc::SetTimerOption(LPCTSTR TimerName, LPCTSTR OptionName, LPCTSTR Value)
{
    CString strTimerName = TimerName;
    CString strValue = Value;
    CTimer * Timer_item;

    // trim spaces from name, make lower-case
    CheckObjectName (strTimerName, false);

    if (!GetTimerMap ().Lookup (strTimerName, Timer_item))
        return eTimerNotFound;

    CString strOptionName = OptionName;

    strOptionName.MakeLower();
    strOptionName.TrimLeft ();
    strOptionName.TrimRight ();

    int iItem;
    int iResult = FindBaseOption (strOptionName, TimerOptionsTable, iItem);

    bool bChanged;

    if (iResult == eOK)
    {
        // this is a numeric option

        // for boolean options, accept "y" or "n"
        if (TimerOptionsTable [iItem].iMinimum == 0 &&
                TimerOptionsTable [iItem].iMaximum == 0)
        {
            if (strValue == "Y" || strValue == "y")
                Value = "1";
            else if (strValue == "N" || strValue == "n")
                Value = "0";
        }

        long iValue = 0;
        double fValue = 0;

        if (strOptionName == "second")
            fValue = atof (Value);
        else
        {
            if (!IsNumber (Value, true))
                return eOptionOutOfRange;

            iValue = atol (Value);
        }

        if (m_CurrentPlugin &&
                (TimerOptionsTable [iItem].iFlags & OPT_PLUGIN_CANNOT_WRITE))
            return ePluginCannotSetOption;  // not available to plugin

        if (TimerOptionsTable [iItem].iFlags & OPT_CANNOT_WRITE)
            return ePluginCannotSetOption;  // not available for writing at all

        iResult = SetBaseOptionItem (iItem,
                                     TimerOptionsTable,
                                     NUMITEMS (TimerOptionsTable),
                                     (char *) Timer_item,
                                     iValue,
                                     bChanged);

        if (bChanged)
        {
            if (!m_CurrentPlugin) // plugin mods don't really count
                SetModifiedFlag (TRUE);   // document has changed
            Timer_item->nUpdateNumber    = App.GetUniqueNumber ();   // for concurrency checks
        }

        if (iResult == eOK && Timer_item->iType == CTimer::eInterval)
        {   // need to set "every" time, not "at" time
            if (strOptionName == "hour")
            {
                Timer_item->iEveryHour = iValue;
                ResetOneTimer (Timer_item);
            } // end of option "hour"
            else if (strOptionName == "minute")
            {
                Timer_item->iEveryMinute = iValue;
                ResetOneTimer (Timer_item);
            } // end of option "minute"
            else if (strOptionName == "second")
            {
                Timer_item->fEverySecond = fValue;
                ResetOneTimer (Timer_item);
            } // end of option "second"

        } // end of need to fiddle with hour/minute/second

        // need to reset if we are changing this
        if (strOptionName == "at_time" && bChanged)
        {
            // copy from at to every or vice-versa
            if (Timer_item->iType == CTimer::eInterval)
            {
                Timer_item->iEveryHour    = Timer_item->iAtHour;
                Timer_item->iEveryMinute  = Timer_item->iAtHour;
                Timer_item->fEverySecond  = Timer_item->fAtSecond;
            }
            else
            {
                Timer_item->iAtHour    = Timer_item->iEveryHour;
                Timer_item->iAtMinute  = Timer_item->iEveryHour;
                Timer_item->fAtSecond  = Timer_item->fEverySecond;
            }
            ResetOneTimer (Timer_item);
        }

        return iResult;

    }  // end of found
    else
    {   // not numeric option, try alpha
        int iResult = FindBaseAlphaOption (strOptionName, TimerAlphaOptionsTable, iItem);
        if (iResult == eOK)
        {

            // alpha option

            if (m_CurrentPlugin &&
                    (TimerAlphaOptionsTable [iItem].iFlags & OPT_PLUGIN_CANNOT_WRITE))
                return ePluginCannotSetOption;  // not available to plugin

            if (TimerAlphaOptionsTable [iItem].iFlags & OPT_CANNOT_WRITE)
                return ePluginCannotSetOption;  // not available for writing at all

            // ------ preliminary validation before setting the option

            if (strOptionName == "script")
            {

                // get Timer dispatch ID

                if (GetScriptEngine () && !strValue.IsEmpty ())
                {
                    DISPID dispid = DISPID_UNKNOWN;
                    CString strMessage;
                    dispid = GetProcedureDispid (strValue, "Timer", TimerName, strMessage);
                    if (dispid == DISPID_UNKNOWN)
                        return eScriptNameNotLocated;
                    Timer_item->dispid  = dispid;   // update dispatch ID
                }
            } // end of option "script"


            // set the option now

            iResult = SetBaseAlphaOptionItem (iItem,
                                              TimerAlphaOptionsTable,
                                              NUMITEMS (TimerAlphaOptionsTable),
                                              (char *) Timer_item,
                                              strValue,
                                              bChanged);

            if (bChanged)
            {
                if (!m_CurrentPlugin) // plugin mods don't really count
                    SetModifiedFlag (TRUE);   // document has changed
                Timer_item->nUpdateNumber    = App.GetUniqueNumber ();   // for concurrency checks
            }

            return iResult;
        }  // end of found alpha option
    }  // end of not numeric option

    return eUnknownOption;
} // end of SetTimerOption
long CMUSHclientDoc::AddTimer(LPCTSTR TimerName,
                              short Hour,
                              short Minute,
                              double Second,
                              LPCTSTR ResponseText,
                              long Flags,
                              LPCTSTR ScriptName)
{
    CString strTimerName = TimerName;
    CTimer * timer_item;
    DISPID dispid = DISPID_UNKNOWN;
    long nStatus;
    bool bReplace = false;

    if (strTimerName.IsEmpty ())
        strTimerName.Format ("*timer%s", (LPCTSTR) App.GetUniqueString ());
    else
        // return if bad name
        if (nStatus = CheckObjectName (strTimerName))
            return nStatus;

    // if it already exists, error
    if (GetTimerMap ().Lookup (strTimerName, timer_item))
        if (Flags & eReplace)
            bReplace = true;
        else
            return eTimerAlreadyExists;


    if (Hour < 0 || Hour > 23)
        return eTimeInvalid;

    if (Minute < 0 || Minute > 59)
        return eTimeInvalid;

    if (Second < 0.0 || Second > 59.9999)
        return eTimeInvalid;

    // can't have a zero time for "every" timers
    if (((Hour == 0) && (Minute == 0) && (Second == 0.0)) &&
            !(Flags & eAtTime))
        return eTimeInvalid;

// get timer dispatch ID

    if (GetScriptEngine () && strlen (ScriptName) != 0)
    {
        CString strMessage;
        dispid = GetProcedureDispid (ScriptName, "timer", TimerName, strMessage);
        if (dispid == DISPID_UNKNOWN)
            return eScriptNameNotLocated;
    }

    // timer replacement wanted
    if (bReplace)
    {
        // the timer seems to exist - delete its pointer
        delete timer_item;

        // now delete its entry
        GetTimerMap ().RemoveKey (strTimerName);
    }

    // create new timer item and insert in timer map
    GetTimerMap ().SetAt (strTimerName, timer_item = new CTimer);

    if ((Flags & eTemporary) == 0)
        if (!m_CurrentPlugin) // plugin mods don't really count
            SetModifiedFlag (TRUE);

    timer_item->nUpdateNumber    = App.GetUniqueNumber ();   // for concurrency checks

    if (Flags & eAtTime)
    {
        timer_item->iAtHour = Hour;
        timer_item->iAtMinute = Minute;
        timer_item->fAtSecond = Second;
        timer_item->iType  = CTimer::eAtTime;
    }
    else
    {
        timer_item->iEveryHour = Hour;
        timer_item->iEveryMinute = Minute;
        timer_item->fEverySecond = Second;
        timer_item->iType  = CTimer::eInterval;
    }

    timer_item->strContents      = ResponseText;
    timer_item->bEnabled         = (Flags & eEnabled) != 0;
    timer_item->bOneShot         = (Flags & eOneShot) != 0;
    timer_item->bTemporary       = (Flags & eTemporary) != 0;
    timer_item->bActiveWhenClosed = (Flags & eActiveWhenClosed) != 0;
    timer_item->strProcedure     = ScriptName;
    timer_item->strLabel         = TimerName;
    timer_item->dispid           = dispid;

    if (Flags & eTimerSpeedWalk)
        timer_item->iSendTo = eSendToSpeedwalk;
    else if (Flags & eTimerNote)
        timer_item->iSendTo = eSendToOutput;

    ResetOneTimer (timer_item);

    SortTimers ();

    return eOK;
}  // end of CMUSHclientDoc::AddTimer