/*
    slforum version 1.x - Simple chat room usable via telnet.
    Copyright (C) 1998-2006 Jeremy D. Monin, Dan Chin, Matt Lochansky
	(forum@shadowlands.org).
    Based on jdkchat version 1.3, J.D.Koftinoff Software, Ltd. (GPL'd)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/** This class added 19990126.2100 to modularize the command handling,
 *  which makes things easier, more efficient, and allows tab completion
 *  on command names.  (jer)
 *
 *  See SLForumUser::HandleUserCommand() comments for what
 *  a user command should do, and what values it should return.
 */

class SLForumUser;
class SLForumRoom;

#include "world.h"  /* for size_t */
#include <math.h>   /* for log10 for roll */
#include "slforum.h"
#include "slforumcmd.h"


int SLForumCmd::c_users
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
  room->who_sort_is_valid = false;  // trigger re-sort
  room->ListUsers (me);
  room->ListAways (me);
  room->who_sort_is_valid = false;  // mark sort as invalid
  return 0;
}


int SLForumCmd::c_exit
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
  me->resetFieldsToDiscon(true);
  return -1;
}


int SLForumCmd::c_away
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {

  // first announce time of away: (19990206.0300)
  {
    time_t tnow = time(NULL);
    room->AnnounceTimestamp (tnow, me->MSG_MOVES, false, "away");
  }

  // now set the actual away message and announce it
  const char* awaymsg;
  if (argc>0)
	  awaymsg=flarg[0].strval;
  else
	  awaymsg=NULL;
  room->BroadCast(AWAY_FROM_USERNAME, me->setAway (awaymsg), me->MSG_MOVES);
  me->Send ("You're marked as away; type something when you get back.",
	me->HINT_AWAY, false);

  if (me->running_cmd)	// if we're under user ctrl (not auto-away), then,
    me->lastkey = me->lastping;   // away'ing is a public action; reset idle

  return 0;
}


int SLForumCmd::c_clear
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
  /* since VT100 clear command won't work, send many blank lines */
  char clr[104];
  memset (clr, '\n', 104);
  clr[0] = '\r';
  me->uWrite ( clr, 104 ); // since can't use ^L to clear
  return 0;
}


int SLForumCmd::c_to
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
  return _i_toalso (room, me, argc, flarg, false);
}


int SLForumCmd::c_also
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
  return _i_toalso (room, me, argc, flarg, true);
}


/** implementation of c_to, c_also
 */
int SLForumCmd::_i_toalso
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg, bool isAlso)
{
  bool was_to_all = (argc==0) || (flarg[0].u == SLF_USER_ALL);
  if (was_to_all && (argc > 0))
      flarg[0].u = NULL;  /* if rest != NULL, must set it back before return
			   * so the command-beginning is restored properly
			   * ("!to all") after line wrap.
			   */

  SLForumUser *usrr;
  if (argc > 0)
	  usrr = flarg[0].u;
  else
	  usrr = NULL;
  char *rest;
  if (argc >= 2)
	  rest = flarg[1].strval;
  else
	  rest = NULL;

  if (rest != NULL) {
    /* just a quick 1-line message... jer added 981016.1120 */
    if (usrr == NULL) {
      // a message to the entire room.

      me->lastkey = me->lastping;   // reset idle time

      // make sure you can always hear your own public messages,
      // even if you're ignoring public.  (19990522.0230)
      int ignoremask = me->pref.ignore_mask;
      me->pref.ignore_mask = 0;
      room->BroadCast (me->name, rest, me->MSG_PUBLIC |
			(me->is_guest ? me->MSG_GUEST : 0) );	/* all */
      me->pref.ignore_mask = ignoremask;
    } else {
      /* whisper */
      bool sent_to_self = false;
      bool sentout;  // has it been sent successfully to _anyone_ ?

      const char *toname = usrr->GetName();

      sentout = ((me->to_bits != 0) && isAlso);
      if (sentout)
	me->_sendOut (rest);	// send to others if also'd

      //// send to all logins of the to'd user ////
      {
	int toidx = usrr->idx_in_room();
	while ( (usrr = room->GetUserIncremental
		 (toname, toidx, false)) != NULL )
	{
	  if (! usrr->CheckBanned(me)) {
	    sentout = true;
	    usrr->Send ( (me->is_guest ? me->MSG_GUEST : 0),
			 me->name, usrr->name, rest);    // to the to'd user
	  if (usrr == me)
	    sent_to_self = true;
	  }
	}
      }
      if ((! sent_to_self) && (sentout))  /* 'sentout' check is for */
      {			                  /* if to'd user banned us. */
	me->ClearEntryLine();   // local display of message
	me->Send (0, me->name, toname, rest); // ( to self )
      }
    }
    if (was_to_all && (argc>0))
	flarg[0].u = SLF_USER_ALL;
    return 1;
  } else {
    /* traditional: change target of further messages */
    if ((usrr != NULL) && isAlso)
      me->sendMessagesAlso (usrr);
    else
      me->sendMessagesTo (usrr);
    if (usrr == NULL)
	    me->Send (HINT_WHISPERDONE_MSG, me->HINT_DID_TO_ALL, false);
	    // "Further messages will be sent to all users."
    else
	    me->Send (HINT_DONEWHISPERING_CMD_MSG, me->HINT_TO_ALL, false);
	    // "Type \"\177\177\" or ESC ESC when you're done whispering :-)"

    return RVAL_ALWAYS_CLEAR;  // !to shouldn't appear on next line if wrapped
  }
  // one of the two returns above will always be called. (19990617.1810)
}


int SLForumCmd::c_accept
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
  if (me->is_guest) {
    me->Send ("Guests can't accept guests, sorry!", 0, false );
  } else if (argc != 1) {
    me->Send ("Accept who?  Usage: \177accept guestname", 0, false);
  } else {
    SLForumUser *usrr = flarg[0].u;
    if (usrr != NULL) {
      if (usrr->IsUserAround() &&
	  (usrr->state == me->STATE_GUEST_CONFIRM) )
      {

	// make sure they're still there:

	usrr->Send ("You have been accepted.");
	if (! usrr->IsUserAround())
	{
	    // this can happen if they give up and leave
	    // while waiting to be accepted, or if IO error.
	  snprintf (me->buf.buffer, USERBUFSIZ-1,
		   "The guest %s has disconnected.", usrr->name );
	  room->Announce (me->buf.buffer, me->MSG_MOVES, true);
	  usrr->resetFieldsToDiscon();
	  return 0;
	}

	// let everyone know they're coming...

	snprintf (me->buf.buffer, USERBUFSIZ-1,
		  "Type '\177kick %s' if the guest gets annoying!",
		  usrr->name );
	room->Announce (me->buf.buffer, me->HINT_KICK, false);
	snprintf (me->buf.buffer, USERBUFSIZ-1,
		  "%s has accepted the guest %s into the room.",
		  me->name, usrr->name );
	room->Announce (me->buf.buffer);
	me->lastkey = me->lastping;   // this is a public action, reset idle

	// now formally welcome them.

	usrr->acceptAndWelcomeUser();
	usrr->listenListAdd (usrr->name);  /* so guest auto-listens for
					    * their own name if ignoring.
					    */
	// let guest know who let 'em in
	snprintf (me->buf.buffer, USERBUFSIZ-1,
		  "%s has accepted you into the room.",
		  me->name );
	usrr->Send (me->buf.buffer, 0, false);  // so guest knows who let 'em in

      } else {
	me->Send ("'accept' can be used only on waiting guests.", 0, false);
      }
    }
  }
  return 0;
}


int SLForumCmd::c_kick
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
  if (argc != 1) {
    me->Send ("Kick who?", 0, false );
    return 0;
  }

  SLForumUser *usrr = flarg[0].u;
    
  if ((usrr != NULL) && usrr->IsUserAround() && ! usrr->isUserGuest()) {
      char kickbuf[63];
      if (-1 == snprintf ( kickbuf, 62, "%s has tried to kick %s at \201",
			   me->GetName(), usrr->GetName()))
	  kickbuf[62] = '\0';

/*
  usrr->ClearEntryLine();
    usrr->uWrite ("\r        -- ");
    usrr->uWrite (me->GetName());
    usrr->uWrite (" has tried to kick you at ");
    usrr->uWrite (sys_ctbuf + 11);
    usrr->uWrite (". --\r\n");
    usrr->RedisplayEntryLine();
*/
	if (0 == (usrr->pref.ignore_mask & me->MSG_BEEP))
	    usrr->uWrite ( "\007", 1 );  // beep!
	{
	    int oldhintmask = usrr->pref.ignore_mask;
	    usrr->pref.ignore_mask &= (~ SLForumUser::MSG_KICK);
	    room->Announce ( SLForumUser::MSG_SYS | SLForumUser::MSG_KICK,
			     kickbuf, false );
	    usrr->pref.ignore_mask = oldhintmask;
	}
	room->Announce ( "Type \177ignore kicks to not hear kick messages.",
			 SLForumUser::HINT_IGN_KICK, false);
  }

  if (me->is_guest) {
    me->Send ("Guests can't kick, sorry!", 0, false );
  } else {
    if ((usrr != NULL) && usrr->IsUserAround()) {

      if (usrr->isUserGuest()) {
	// do it

	snprintf (me->buf.buffer, USERBUFSIZ-1,
		  "%s has kicked %s out the door at \201.",
		  me->name, usrr->name );
	room->Announce (me->MSG_MOVES, me->buf.buffer);
	usrr->kick("kicked");

	me->lastkey = me->lastping;   // reset idle time

      } else {
	me->Send ("Only guests can be kicked.", 0, false );
      }
    } else {
      // 8 spaces; can't use \t, it won't erase.
      snprintf (me->buf.buffer, USERBUFSIZ-1,
		"        -- User %s cannot be kicked (state=%d). --",
		usrr->GetName(), usrr->state );
      me->Send (me->buf.buffer, 0, false );
    }
  }
  return 0;
}


			// jer added sometime before 980830.2000,
			// and totally rewrote (finally) 20000218.2300
int SLForumCmd::c_msgchk
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
  if (me->is_guest) {
    if (me->running_cmd)
      me->Send ("Guests have no mailbox.", 0, false);
    return 0;
  }

  time_t newm;
  if (! i_msgchk_check(me, newm))
  {
    me->Send ("You have old mail waiting.", 0, false);
  } else {
    me->auto_msgchk_lastnew = newm;
    me->auto_msgchk_lastnew_is_valid = true;
    i_msgchk_printmsg(me, newm, false);
  }
  return 0;
}


void SLForumCmd::i_msgchk_printmsg(SLForumUser *me, time_t newmailt, bool beep)
{
  char msgbuf[128];
  newmailt += (60 * 60 * me->pref.jetlag);
  struct tm *tmnow;
  tmnow = localtime (&newmailt);
  if (tmnow == NULL)
  {
    me->Send ("You have new mail.", 0, beep);
    return;
  }
      
  strftime (msgbuf, 127,
	    "You have new mail; most recent received %d %b at %H:%M.",
	    tmnow);
  me->Send (msgbuf, 0, beep);
}


bool SLForumCmd::i_msgchk_check
	(SLForumUser *me, time_t &newmailt)
{
  // get file stats.
  struct stat mboxfstat;
  {
    char inboxfname[256];
    if (-1 == snprintf (inboxfname, 255, MSGCHK_INBOXFNAME, me->name)) {
      me->Send
	("Path to INBOX too long!  Sorry... bug your sysadmin.", 0, false);
      return false;
    }
    
    if (0 != stat (inboxfname, &mboxfstat)) {
      if (me->running_cmd) {
	if (errno == ENOENT)
	  me->Send("You have no mailbox.", 0, false);
	else {
	  me->Send
	    ("Sorry, could not stat your inbox:", 0, false);
	  me->Send
	    (strerror(errno), 0, false);
	}
      }
      return false;
    }
  }

  // Interpret.
  if (mboxfstat.st_mtime <= mboxfstat.st_atime)
  {
    return false;
  } else {
    newmailt = mboxfstat.st_mtime;
    return true;
  }
}


				// jer added 980830.2000 (!date, !time)
int SLForumCmd::c_time
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
  char buf[48];
  const char *pctime;
  time_t tnow = time(NULL);
  if (me->pref.jetlag != 0)
      tnow += (60 * 60 * me->pref.jetlag);
  pctime = ctime ( &tnow );
  if (pctime == NULL) {
    room->Announce
      ("cmd:L237: assert failed in 'time': ctime == NULL :-(");
  } else {
    if (me->pref.jetlag==0) room->logprintf (pctime);
    sprintf (buf, "It's now %s", pctime);
    buf[ strlen(buf)-1 ] = 0;  // chop \n
    me->Send ( buf, 0, false );
  }
  return 0;
}



/** impl. of c_ignore, c_unignore
 */
int SLForumCmd::_i_ignore (SLForumRoom *room, SLForumUser *me,
			   const char *rest, bool setToIgnore)
{
  int newignores;

  // using strncasecmp handily allows us to ignore case and
  // plural/singular distinction.

  if (rest != NULL) {
    if (0 == strncasecmp ("guest", rest, 5)) {
      newignores = me->MSG_GUEST; 
    } else if (0 == strncasecmp ("public", rest, 6)) {
      newignores = me->MSG_PUBLIC;
    } else if (0 == strncasecmp ("all", rest, 3)) {
      newignores = 0xffffffff;
    } else if (0 == strncasecmp ("none", rest, 4)) {
      newignores = 0;
    } else if (0 == strncasecmp ("beep", rest, 4)) {
      newignores = me->MSG_BEEP;
    } else if (0 == strncasecmp ("kick", rest, 4)) {
      newignores = me->MSG_KICK;
    } else if (0 == strncasecmp ("spammed", rest, 7)) {
      newignores = me->MSG_BANNERED;
    } else if (0 == strncasecmp ("spam", rest, 4)) {
      newignores = me->MSG_BANNER;
    } else if (0 == strncasecmp ("roll", rest, 4)) {
      newignores = me->MSG_ROLL;
    } else if (0 == strncasecmp ("move", rest, 4)) {
      newignores = me->MSG_MOVES;
    } else if (0 == strncasecmp ("idlesoon", rest, 8)) {
      newignores = me->MSG_IDLESOON;
    } else if (0 == strncasecmp ("privbeep", rest, 8)) {
      newignores = me->MSG_P_BEEP;
    } else if (0 == strncasecmp ("chime", rest, 5)) {
      newignores = me->MSG_HCHIME;
    } else if (0 == strncasecmp ("emote", rest, 5)) {
      newignores = me->MSG_EMOTE;
    } else if (0 == strncasecmp ("8ball", rest, 5)) {
      newignores = me->MSG_8BALL;
    } else {
      if (setToIgnore)
	me->Send
	  ("should be \177ignore public or guest[s] or beep[s] or idlesoon or", 0, false);
      else
	me->Send
	  ("should be \177unignore public or guest[s] or beep[s] or idlesoon or", 0, false);

      me->Send
	  ("spam or roll[s] or movement or privbeep or 8ball or", 0, false);
      me->Send
	  ("chime[s] or emote[s] or spammed or kick[s] or all or none", 0, false);

      return 0;
    }

    if (setToIgnore) {
      if (newignores == 0)
        me->pref.ignore_mask = 0;
      else
        me->pref.ignore_mask |= newignores;
    } else {
		// clear ignores
      if (newignores == 0)
	me->pref.ignore_mask = (~0);  // 0xffffffff;
      else
        me->pref.ignore_mask &= (~newignores);
    }

    if (me->running_cmd)  me->savePrefs();
  }  // end if-actually-changing-anything

  // print out current (or new) settings.
  {
    newignores = me->pref.ignore_mask;

    char currset[140];
    sprintf (currset, "---- currently ignoring ");
    if ( newignores == 0 )
      strcat (currset, "none ");
    else {
      if (newignores & me->MSG_PUBLIC)
	strcat (currset, "public ");
      if (newignores & me->MSG_GUEST)
	strcat (currset, "guests ");
      if (newignores & me->MSG_BEEP)
	strcat (currset, "beeps ");
      if (newignores & me->MSG_P_BEEP)
	strcat (currset, "privbeeps ");
      if (newignores & me->MSG_MOVES)
	strcat (currset, "movement ");
      if (newignores & me->MSG_BANNER)
	strcat (currset, "spam ");
      if (newignores & me->MSG_IDLESOON)
	strcat (currset, "idlesoon ");
      if (newignores & me->MSG_BANNERED)
	strcat (currset, "spammed ");
      if (newignores & me->MSG_ROLL)
	strcat (currset, "rolls ");
      if (newignores & me->MSG_HCHIME)
	strcat (currset, "chimes ");
      if (newignores & me->MSG_EMOTE)
	strcat (currset, "emotes ");
      if (newignores & me->MSG_KICK)
	strcat (currset, "kicks ");
      if (newignores & me->MSG_8BALL)
	strcat (currset, "8ball ");
    }
    strcat (currset, "----");
    me->Send (currset, 0, false);
  }
  return 0;
}

int SLForumCmd::c_ignore
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
  const char *strval = (argc > 0) ? flarg[0].strval : NULL;
  return _i_ignore (room, me, strval, true);
}


int SLForumCmd::c_unignore
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
  const char *strval = (argc > 0) ? flarg[0].strval : NULL;
  return _i_ignore (room, me, strval, false);
}


int SLForumCmd::c_help
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
  const char *parm, *desc;

  const slf_cmd_desc *cmdn;
  if (argc > 0)
    cmdn = flarg[0].cmd;
  else
    cmdn = NULL;

  // valid command?
  if (cmdn != NULL) {
    parm = cmdn->parmhelp;
    desc = cmdn->deschelp;
  } else {
    parm = desc = NULL;
  }

  if ((cmdn == NULL) || (desc == NULL)) {
    // print out general help message
    // 981008.1950: first, don't be rude if they type !help
    if ((cmdn != NULL) ||
	((strlen(me->buf.buffer) > 1)
	&& (strncmp ((me->buf.buffer)+1, "help", 4 ) != 0 )))
    {
      me->Send ("Sorry, no such command.", 0, false );
    }
    me->uWriteSubst (me->commands);
    me->rndCommand();
  } else {
    // print out...
    me->ClearEntryLine();
    me->uWrite ("-- slforum help : ");
    me->uWrite (cmdn->name);
    if (parm != NULL) {
      me->uWrite (" ");
      me->uWrite (parm);
    }
    me->uWrite (" : --\r\n");
    me->uWrite (desc);
    me->uWrite ("\r\n");
  }
  me->RedisplayEntryLine();
  me->resetFrom();        // 19990224.2315 dschin (so that next line is preceded by from:)

  return 0;
}


int SLForumCmd::c_banner
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {

#ifdef GUESTS_CANNOT_BANNER
  if(me->is_guest) {
    me->Send ("Sorry, guests cannot banner.", 0, false);
    return 0;
  }
#endif

  int ignor = me->pref.ignore_mask;		// so we hear our own spam
  me->pref.ignore_mask = 0;			// 19990522.0230
  me->lastkey = me->lastping;		// reset idle time
  int retv = room->doBanner (me);
  me->pref.ignore_mask = ignor;
  if (retv > 0) {
    char waitmsg[64];
    sprintf (waitmsg, "Must wait another %d seconds to spam.",
	     retv );
    me->Send (waitmsg, 0, false);
  } else if (retv == -1) {
    room->Announce ("while doing spam:", 0, false);
    room->Announce (strerror(errno), 0, false );
  }
  return 0;
}


/** for c_listen, c_ban... 20000610.1100 */
int SLForumCmd::i_listmanip
  (LinkedList* &llist,
   SLForumRoom *room, SLForumUser *me,
   int argc, slfparam *flarg,
   int maxlistsize,
   ptr_slfu_member_func addlistmethod,
   const char *listerlabel)
{

  if (argc == 0) {
    me->Send ("Need a command word (clear, none, list, add)", 0, false);
    return 0;
  }

  if (me->is_guest) {
    me->Send ("Guests can't do this.", 0, false);
    return 0;
  }

  char *subcmd = flarg[0].strval;


  if ((0 == strcasecmp(subcmd, "clear"))
      || (0 == strcasecmp(subcmd, "none")))
  {
    if (llist != NULL) {
      llist_free (llist);
      llist = NULL;
    }
    me->Send ("List cleared.", 0, false);
    if (me->running_cmd)  me->savePrefs();

  } else if (0 == strcasecmp(subcmd, "list")) {

    me->ClearEntryLine();
    me->uWrite(listerlabel);
    if (llist != NULL) {
      LinkedListLink *LL;
      for ( LL = llist_head (llist);
	    LL != NULL;
	    LL = llist_next (llist, LL) ) {
	me->uWrite("\t");
	me->uWrite((const char *)llist_ldata(LL));
      }
      me->uWrite("\r\n");
    } else
      me->uWrite("\t-- none --\r\n");

  } else {
    // assume we're adding
    
    char *wrd;
    bool first_word_is_add_subcmd = false;
    
    if (0 == strncasecmp(subcmd, "add", 3)
	&& ((*(subcmd+3) == ' ') || (*(subcmd+3) == '\0')))
      first_word_is_add_subcmd = true;  // usually it won't be
    
    for (int currarg = 0 ; currarg < argc; ++currarg)
    {
      wrd = flarg[currarg].strval;
      if (wrd == NULL) {
	if (currarg == 0) 
	  me->Send ("What word(s) to add?", 0, false);
	return 0;
      }

      register char *wordstop;
      bool more_words;
      
      // first, change any commas to spaces
      wordstop = wrd;
      while ((wordstop = index(wordstop, ',')) != NULL)
	*wordstop = ' ';
      
      // ditto for slashes
      wordstop = wrd;
      while ((wordstop = index(wordstop, '/')) != NULL)
	*wordstop = ' ';
    
      // now, potentially add many words. whitespace separates.
      do {
	for (wordstop = wrd; (*wordstop != ' ') && (*wordstop != '\0');
	     ++wordstop )
	  ;
	if (*wordstop != '\0') {
	  more_words = true;
	  *wordstop = '\0';   // temporarily mark end of word
	} else {
	  more_words = false;  // no more words in this param; maybe next parm?
	}
  
	// about to actually add it.
	
	if (first_word_is_add_subcmd)
	  first_word_is_add_subcmd = false;  // string is "add blah blah"
	else {
	  
	  // check size first:
	  if (llist_length (llist) >= maxlistsize)
	  {
	    char erbuf[80];
	    sprintf(erbuf, "Your list is already at the maximum size of %d.",
		    maxlistsize);
	    me->Send (erbuf, 0, false);
	    return 0;
	  }
	  
	  (me->*addlistmethod) (wrd);  // actually add it
	}
	
	if (more_words) {
	  *wordstop = ' ';  // un-null end of word
	  while (*wordstop == ' ')    // skip spaces
	    ++wordstop;
	  more_words = (*wordstop != '\0');
	  wrd = wordstop;   // and move to next argument
	} 
      } while (more_words);  // end within-each-arg-string

    }  // end for-each-arg-string

    if (me->running_cmd)  me->savePrefs();

  }  // end subcmd-selector

  return 0;
}


// jer 19990215.1900
/** Manipulate the listen list */
int SLForumCmd::c_listen
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg)
{

  return i_listmanip
    (me->pref.listenlist, room, me, argc, flarg,
     LISTENLIST_MAXSIZE, &SLForumUser::listenListAdd,
     "\r\t-- List of words we're listening for: --\r\n");
}





// !beep <person>
// added 19990323.0200 dschin
// 19990603.2340 - jer - now beeps all logins of that user, not just
//	the first one it finds.
// 20000129.1010 - jer - can now beep multiple users.
int SLForumCmd::c_beep
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {

  if (argc == 0) {
      me->Send("beep who?",0,false);
      return 0;
  }

  SLForumUser *usrr;

  for (int i = 0; i < argc; ++i) {

    usrr = flarg[i].u;

    char fromto[60];
    bool beeping_self = false;

    room->logprintf("(%s beeps %s)\n",me->GetName(),usrr->GetName());
    sprintf(fromto,"%s beeps %s at \201",
	    me->GetName(),usrr->GetName());
    {
      int i = 0;
      const char *tousername = usrr->GetName();
      // find and beep all logins of that user
      SLForumUser *to_u;
      while (NULL != (to_u = room->GetUserIncremental (tousername, i, false))){
	if(to_u->pref.ignore_mask & to_u->MSG_P_BEEP){
	  me->Send
	    ("User is ignoring private beeps. Silently beeping them.",
	     0,false);
	  to_u->Send(to_u->MSG_P_BEEP, fromto, false);
	}
	else {
	  to_u->Send(to_u->MSG_P_BEEP, fromto, true);
	}
	if (me == to_u)
	  beeping_self = true;				// 19990606.0125
      }  // end while
    }  // end send-to-all section

    if (! beeping_self)
      me->Send(fromto,0,true);
  }  // end if-usrr-null
  return 0;
}  // end c_beep


// 19990330.0245 dschin- allows a !! abbreviation for !to all when away
// 19990529.1800 jer - because we now autoparse args,
//	this is now a one-liner.
int SLForumCmd::c_toall
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {

  slfparam nuargs[2];
  nuargs[0].u = SLF_USER_ALL;
  if (argc > 0)
    nuargs[1].strval = flarg[0].strval;
  else
    nuargs[1].strval = NULL;

  int retval = c_to (room, me, argc+1, nuargs);	// send message to all

  if (retval > 0)
    retval = 0;
  return retval;
}



// Roll cmd by dschin@syr.edu
/* Dice rolling- Dan Chin 19990124.0110 
 * Syntax: !roll <num-die> 
 * Comment out GUESTS_CAN_ROLL to disable guest rolling 
 * ability 
 * changed uWrite() to Send()  19990125.1420 
 * First number is wild die... now denotes that.
 *****************************
 * 2/24/99, 0245
 *      Moved roll into slforumcmd.
 *      Simplified branching. Now returns immediately upon failure and
 *      printing an appropriate error msg
 *
 * 19990922.1145: dschin ifdeffed out the SW RPG rules, and decreased
 *	number of lines !roll requires.
 */
int SLForumCmd::c_roll
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg)
{
  
  int	  numDice=0,d=0,i=0;
  unsigned long long total=0L;
  int     nside;  // me->pref.dice_n_sides unless specified on cmd line
#ifdef ROLL_SWRPG
  int	  high=0;
  short	  comp=0; /* 1 if wild die is 1, 2 if complication, 0 else */
#endif
  /*  char	  rollStr[100]; */
  char	  tmp[80];
  char    buffer[80];
  
#ifdef GUESTS_CANNOT_ROLL
  if(me->is_guest) {
    me->Send ("Sorry, guests cannot roll.", 0, false);
    return 0;
  }
#endif

#ifdef ROLL_SWRPG
#define ROLL_REALMAX ROLL_MAXDSHOW
#else
#define ROLL_REALMAX ROLL_MAXDICE
#endif

  // How many dice? If invalid, return!!
  if(argc >= 1) {
    numDice = flarg[0].intval;
    if (argc == 2)
	    nside = flarg[1].intval;
    else
	    nside = me->pref.dice_n_sides;
    if (nside < 0) nside = -nside;

    if (nside == 0) {
	    me->Send("Number of sides must be nonzero.", 0, false);
	    return 0;
    }
  } else {
    me->Send("Usage: \177roll <number of dice> [<number of sides>]", 0, false);
    return 0;
  }
  if(numDice > ROLL_REALMAX){     // limit number of dice for buffer
    sprintf (tmp, "Too many dice.  Roll at most %d", ROLL_REALMAX);
    me->Send(tmp, 0, false);
    return 0;
  }

  if(numDice <= 0) {
	  me->Send("Please enter a positive number", 0, false);
	  return 0;
  }

  int ign = me->pref.ignore_mask;	// so we hear our own roll
  me->pref.ignore_mask = 0;		// 19990522.0230

  me->lastkey = me->lastping;	// reset idle time

  {
	  const char *verb;  // will say "rolls" or "flips"
	  const char *dbuf;  // will say "dice" or "die" or "D 8" or whatnot
	  char dbufbuf[16];
	  if (nside == 6) {
		  dbuf = numDice!=1 ? "dice" : "die" ;  // static string
		  verb = "rolls";
	  } else if (nside == 2) {
		  dbuf = numDice!=1 ? "coins" : "coin" ;  // static string
		  verb = "flips";
	  } else {
		  dbuf = dbufbuf;   // point it at dynamic buffer
		  if (-1 == snprintf
		      (dbufbuf, 16, "D %d", nside))
			  dbufbuf[15] = '\0';
		  verb = "rolls";
	  }
	  sprintf (buffer, "%s %s %d %s:", me->name, verb, numDice, dbuf);
  }

  // It's now time to figure out how much room we have on the line to print
  // individual dies (or coins)' rolls.  The rest of the line is taken up by
  // the "FORUM:" left side (12 chars), the "jeremy rolls 10 dice:" text,
  // and the "= 37" total.
  int roomleft = 78 - 12 - strlen(buffer);  // left side, "rolls" text
  {
	  int totaldigits = (int) log10 (nside * (long long) numDice);
	  if (nside != 2)  // not coins, thus "= total"
		  totaldigits += 2;  // "= "
	  else
		  totaldigits += 8;  // "= " " heads"
	  roomleft -= totaldigits;
  }


  // Now we start rolling.
  if(numDice>0)
  {
#ifdef ROLL_SWRPG
    /* Roll the first dice */
    ++i;
    d = rand() % nside;  if (nside!=2) ++d;  // dice are 1..n, coins are 0..1
    high=d;
    total+=(unsigned long long)d;
    if (nside != 2) sprintf(tmp," <%d>",d);
    else            sprintf(tmp," <%c>", d ? 'H' : 'T');
    strncat(buffer,tmp, USERBUFSIZ-1);
    roomleft -= strlen(tmp);
    if(d==1)
      comp=1;
      
    while(d == nside){
      d=rand() % nside;  if (nside!=2) ++d;
      total+=(unsigned long long)d;
      if (nside != 2) sprintf(tmp," [%d]",d);
      else            sprintf(tmp, "[%c]", d ? 'H' : 'T');
      strncat(buffer,tmp, USERBUFSIZ-1);
      roomleft -= strlen(tmp);
    }
#endif
      
    /* roll the rest */
    for(;i<numDice;++i)
    {
      d=rand() % nside;  if (nside!=2) ++d;
#ifdef ROLL_SWRPG
      if(d>high)
	high=d;
#endif
      total+=(unsigned long long)d;

      // print this roll to a buffer, then figure how much room it needs.
      // If it takes too much room, put ellipsis ("...") instead.
      if (roomleft > 0)
      {
		  if (nside!=2) sprintf (tmp, " %d", d);
		  else          sprintf (tmp, " %c", d ? 'H' : 'T');
		  int bsize = strlen (tmp);
		  if ( (roomleft - bsize) <= 3)  // the 3 is for "..."
		  {
			  strcpy (tmp, "...");  // overwrite tmp with "..."
			  roomleft = 0;  // flag that we're all full
		  }
		  else
			  roomleft -= bsize;
		  strncat (buffer, tmp, USERBUFSIZ-1);
      }
	
#ifdef ROLL_SWRPG
      /* Change the complication flags */
      if((comp==1)&&(d==nside))
	comp=3;
      else if(comp==1)
	comp=2;
#endif
    }
      
#ifdef ROLL_SWRPG
    /* Subtract any complications */
    if(comp==2){
      total-=(unsigned long long)(high+1);
      sprintf(tmp," -%d",high);
      strncat(buffer,tmp, USERBUFSIZ-1);
    }
#endif
    /* output */

    if (nside != 2)
	    sprintf (tmp, " = %llu", total);
    else if (total != 1L)
	    sprintf (tmp, " = %llu heads", total);
    else
	    sprintf (tmp, " = %llu head", total);
    strncat (buffer, tmp, USERBUFSIZ-1);
    room->Announce (me->MSG_ROLL | me->MSG_PUBLIC, buffer);
#ifdef ROLL_SWRPG
    if(comp==3)
      room->Announce (me->MSG_ROLL | me->MSG_PUBLIC,
		      "There was a complication.");
#endif

  }  // end if
  me->pref.ignore_mask = ign;   // restore mask after we hear our own roll

  return 0;
}


// print out who we're !to-ing.
// jer 19990623.2000
int SLForumCmd::c_printwho
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {

  const char *nam = "", *n2;
  SLForumUser *u;
  bool printed_start = false;

  for ( int bit = 0; bit < MAX_CHAT_USERS; ++bit) {
    if (  me->to_bits & (1 << bit) ) {

      u = room->GetUserByIndex(bit);
      if (u != NULL) {
	n2 = u->GetName();
	if (strcmp (nam, n2)) {
	  nam = n2;
	  if (! printed_start) {
	    me->ClearEntryLine();
	    me->uWrite ("You're whispering to:");
	    printed_start = true;
	  }
	  me->uWrite ("\t");
	  me->uWrite (nam);
	}
      }
    }
  }

  if (printed_start)
    me->uWrite ("\r\n");
  else
    me->Send("You're talking to everyone (whispering to no one).", 0, false);
  return 0;
}


/** Rot-13 first arg and make it the buffer. */
int SLForumCmd::c_rot13
   (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {

  if ((argc < 1) || (flarg[0].strval == NULL))
    return RVAL_ALWAYS_CLEAR;

  char rotbuf[USERBUFSIZ];
  {
    char *srcp;
    char rotamt;
    register char *bufp;
    register char c = flarg[0].strval[0];
    rotamt = (char) (me->pref.rot_offset);
    bufp = rotbuf;
    srcp = flarg[0].strval;
    while (c != '\0') {
      if ((c>='A') && (c<='Z'))
      {
	c += rotamt;
	if (c > 'Z')
	  c -= 26;
      } else if ((c>='a') && (c<='z'))
      {
	c += rotamt;
	if (c > 'z')
	  c -= 26;
      } else if (((c >= '0') && (c <= '9')) && me->pref.rot10_digits_too) {
	if (c < '5')
	  c += 5;
	else
	  c -= 5;
      }
      
      *bufp = c;
      ++srcp;
      ++bufp;
      c = *srcp;
    }  // end while
    *bufp = '\0';
  }

  // now put it in the buffer
  strcpy (me->buf.buffer, rotbuf);
  me->buf.cur_len = me->stringDisplayLength
      (me->buf.buffer, &(me->buf.cur_pos));

  // don't erase any of input buffer, since we just set it :-)
  return RVAL_RETAIN_BUFFER;
}


/** Either just say "Sorry, topics are disabled." or, if they're admin,
 *  hintmsg them that they can !admin topics ON.  20020803.0115 jdm
 */
static void _i_sendTopicsDisabledMsg (SLForumUser *me)
{
  //        "Sorry, topics are disabled."
  me->Send (TOPICS_DISABLED_MSG, 0, false);
#ifdef ALLOW_TOPICS
  if (me->isAdmin())
  {
    // "As an admin, you may enable topics: type \177admin topics on"
    me->Send (TOPICS_ADMIN_ON_MSG,
	      SLForumUser::HINT_ADMIN_TOPICS_ON, false);
  }
#endif  // ALLOW_TOPICS
}



//by MaTTeD, 19990731.2245
/** Allows a user to define or print the topic for the forum room. */
int SLForumCmd::c_topic
(SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
#ifndef ALLOW_TOPICS
      _i_sendTopicsDisabledMsg (me);
      return 0;
#else
      if (! room->topicsAreOn())
      {
	      _i_sendTopicsDisabledMsg (me);
	      return 0;
      }

  if (argc > 0)  // If you want to set a new topic
  {
    char	s[128];
    char	*pctime;
    time_t	tleft = room->topicTimeCheck(me);
    char	*rest = flarg[0].strval;
    int	min, sec;

#ifdef GUESTS_CANNOT_SET_TOPIC
    if(me->is_guest){
      me->Send("\r\nSorry, guests cannot set the topic\r\n");
      return 0;
    }
#endif

    pctime = ctime(&tleft);
    if (pctime == NULL) {
      room->Announce
	("user:L934: assert failed in 'topic': ctime == NULL :-(");
      return 0;
    }

    pctime[16] = '\0';  // snip seconds and year
    min = (tleft / 60);
    sec = (tleft % 60);

    if ( tleft == (time_t) 0)  //if you can set topic
    {

      room->setTopic (rest, me);
      me->lastkey = me->lastping;	// reset idle time
      // "_You_ can change your topic at any time."
      me->Send (TOPIC_CHANGE_ANYTIME_MSG,
		me->HINT_CHG_TOPIC_ANYTIME, false );

    } else {  // you can't set topic yet, due to timeout
      // "Topic set at %s by %s."
      sprintf (s, TOPIC_MSG_BRIEF_FMT, pctime+11, room->getTopicSetter());
      me->Send(me->MSG_SYS, SYSMSG_FROM_USERNAME, NULL, s );
      // "Topic can't be changed for another %d min %d sec"
      sprintf
	(s, TOPIC_NO_CHANGE_YET_FMT, min, sec);
      s[63] = '\0';
      me->Send( me->MSG_SYS, SYSMSG_FROM_USERNAME, NULL, s );
    }
  }
     
  else   // You are just looking at what the current topic is
  {   
    room->listTopic(NULL, me, true);
  }   
 return RVAL_ALWAYS_CLEAR;
#endif
}


/* This will allow someone to make a comment for the topic */
/* Added by MaTTeD 19990731.2245   */
                
int SLForumCmd::c_comment
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg)
{
#ifndef ALLOW_TOPICS
      _i_sendTopicsDisabledMsg (me);
      return 0;
#else
      if (! room->topicsAreOn())
      {
	      _i_sendTopicsDisabledMsg (me);
	      return 0;
      }
                
 if (argc > 0)  // If you want to set a new comment
 {
#ifdef GUESTS_CANNOT_COMMENT_TOPIC
   if(me->is_guest){
     me->Send("\r\nSorry, guests cannot comment on the topic\r\n");
     return 0;
   }
#endif

const char      *rest = flarg[0].strval;
 
   room->Announce (me->MSG_MOVES, "comment set at \201:");  //announces the time
  
   // topicAddComment will return the formatted comment string,
   // so we announce that to the room.

   room->BroadCast (COMMENT_FROM_USERNAME,
		    room->topicAddComment (rest, me),
		    SLForumUser::MSG_MOVES);

  me->lastkey = me->lastping;	// reset idle time
 }
 else
 {   // No arguments; just print the current topic with comments.
   room->listTopic(NULL, me, true);
 }              
 return RVAL_ALWAYS_CLEAR;
#endif
} 


// jer 19990814.2145
/** List all previous topics (As many as we remember), or list one of
 *  these with its comments.
 */
int SLForumCmd::c_prevtopic
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
#ifndef ALLOW_TOPICS
      _i_sendTopicsDisabledMsg (me);
#else  
      if (! room->topicsAreOn())
      {
	      _i_sendTopicsDisabledMsg (me);
	      return 0;
      }

  char s[80];
  if (argc == 0)
  {
    bool had_prevs = false;

    // just list prev topics
    for (int i = 0; i < NUM_REMEMBERED_TOPICS; ++i)
      if (room->prevtopics[i].tname[0] != '\0')
      {
	// sprintf (s, "\r        -- Previous topic #%d is: --\r\n", (i+3));
	// me->uWrite (s);
	if ((room->prevtopics[i].tcomments != NULL) ||      // had comments?
	    strcmp (room->prevtopics[i].tsetter, "nobody")) // not dummy topic?
	{
	  room->listTopic (&(room->prevtopics[i]), me, false, i+1);
	  had_prevs = true;
	}
      }

    if (had_prevs)
      me->Send ("Type \177prevtopic n to list comments for previous topic # n.", me->HINT_PREVTOPIC, false);
    else
      me->Send ("No previous topics or comments.", 0, false);

  } else {
    // getting info on a prev topic
    int p1val = flarg[0].intval;
    if (p1val > NUM_REMEMBERED_TOPICS) {
      sprintf (s,"There are only %d remembered topics.", NUM_REMEMBERED_TOPICS);
      me->Send (s, 0, false);
      return 0;
    }
    if (room->prevtopics[p1val - 1].tname[0] == '\0') {
	    // "There are not this many previous topics yet."
	    me->Send  ( TOPIC_PREV_NOT_YET_FMT, 0, false );
	    return 0;
    }

    //sprintf (s, "\r        -- Previous topic #%d is: --\r\n", p1val);
    //me->uWrite (s);
    room->listTopic (&(room->prevtopics[p1val - 1]), me, true, p1val);
  }
#endif
  return 0;
}





// show users logged in, but not their away messages
int SLForumCmd::c_who
(SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg)
{
    if (argc == 0)
    {
	/* list all users */
	room->who_sort_is_valid = false;  // trigger re-sort
	room->ListUsers(me);
	room->who_sort_is_valid = false;  // mark sort as invalid
    } else {
	/* details on one user (all logins of course) */
	SLForumUser *usrr = flarg[0].u;
	const char *toname = usrr->GetName();
	int toidx = usrr->idx_in_room();
	while ( (usrr = room->GetUserIncremental
		 (toname, toidx, false)) != NULL )
	{
	    usrr->printLoginDetails (me);
	}
    }

  return 0;  
}


// just shows users' away messages.
int SLForumCmd::c_showaway
(SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg){
  room->who_sort_is_valid = false;  // trigger re-sort
  room->ListAways(me, true);
  room->who_sort_is_valid = false;  // mark sort as invalid
  return 0; 
}


// used by c_zebra: reads back or sets an integer variable
//
int SLForumCmd::_try_num_param
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg,
   const char *paramname, const char *paramdescname,
   int &val, char *sbuf, int scale /* =1 */ )
{

  if (argc == 0) // shouldn't happen due to where we're called from,
    return -1;   // but check it just the same.
  if (0 != strcasecmp(paramname, flarg[0].strval))
    return -1;

  if ((argc == 1) || (flarg[1].strval[0] == '\0')) {
    sprintf (sbuf, "Current %s is %d.", paramdescname, val/scale);
    me->Send (sbuf, 0, false);
    return 0;
  }

  if (argc == 2) {
    int nuval = atoi (flarg[1].strval);
    val = nuval*scale;
    if (me->running_cmd) {
      sprintf (sbuf, "%s changed to %d.", paramdescname, nuval);
      me->Send (sbuf, 0, false);
      me->savePrefs();
    }  // else we're loading from the file for user login.
    return 0;
  }

  me->Send ("Value must be a single integer; try again.");
  return SLForumCmd::RVAL_RETAIN_BUFFER;
}


// used by c_zebra: reads back or sets a boolean variable
//
int SLForumCmd::_try_bool_param
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg,
   const char *paramname, const char *paramdescname,
   bool &val, char *sbuf) {

  if (argc == 0) // shouldn't happen due to where we're called from,
    return -1;   // but check it just the same.
  if (0 != strcasecmp(paramname, flarg[0].strval))
    return -1;

  if ((argc == 1) || (flarg[1].strval[0] == '\0')) {
    sprintf (sbuf, "Current %s is %s.", paramdescname, val ? "Yes" : "No");
    me->Send (sbuf, 0, false);
    return 0;
  }

  if (argc == 2) {
    bool nuval = false;  // must init or gcc 2.95 complains
    bool nuval_set = false;
    if (flarg[1].strval[1] == '\0') {
      char ch = flarg[1].strval[0];
      if ((ch == '1') || (ch == 'Y') || (ch == 'y')
	  || (ch == 'T') || (ch == 't')) {
	nuval = true;
	nuval_set = true;
      } else if ((ch == '0') || (ch == 'N') || (ch == 'n')
		 || (ch == 'f') || (ch == 'F')) {
	nuval = false;
	nuval_set = true;
      }
    } else {
      if ((0 == strcasecmp (flarg[1].strval, "true"))
	  || (0 == strcasecmp (flarg[1].strval, "yes"))
	  || (0 == strcasecmp (flarg[1].strval, "on"))) {
	nuval_set = true;
	nuval = true;
      } else if ((0 == strcasecmp (flarg[1].strval, "false"))
		 || (0 == strcasecmp (flarg[1].strval, "no"))
		 || (0 == strcasecmp (flarg[1].strval, "off"))) {
	nuval_set = true;
	nuval = false;
      }
    }
    if (nuval_set) {
      val = nuval;
      if (me->running_cmd) {
	sprintf
	  (sbuf, "%s changed to %s.", paramdescname, nuval ? "Yes" : "No");
	me->Send (sbuf, 0, false);
	me->savePrefs();
      }   // else we're loading from prefs file on user login
      return 0;
    }
  }

  me->Send
    ("Value must be a single yes/no (on/off, true/false) value; try again.");
  return SLForumCmd::RVAL_RETAIN_BUFFER;
}




 // Sets user preferences (the !set command)
 // was called 'zebra' not c_set because that way, nothing will show up later
 // w/ the same first letter, making tab-completion easy :-)
 //
 // IF YOU ADD A PREFERENCE: Be sure to edit SLForumUser::savePrefs
 //	so it saves your preference out to disk!
 //
 // jer 19991028
 int SLForumCmd::c_zebra
   (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg)
 {

   char s[80];

   // if you add any user prefs, make sure to update SLForumUser::savePrefs()
   // and the help message and arglist in the slforumuser::usercmds table.
   if (argc == 0)
   {
     me->Send ("Your current settings:", 0, false );

     sprintf
       (s, "\tcommand    %c\tCurrent command-prefix character", me->pref.cmd_char);
     me->Send ( s, 0, false );

     if (me->pref.cmd_char_alt != '\0')
     {
       sprintf
	 (s,
	  "\taltcmd     %c\tAlternate command-prefix character",
	  me->pref.cmd_char_alt);
       me->Send ( s, 0, false );
     }

     sprintf
       (s, "\tautoaway   %d\tMin idle to auto-\177away",
	me->pref.auto_away_seconds/60);
     me->Send ( s, 0, false );

#ifdef ALLOW_TOPICS
      if (room->topicsAreOn())
      {
     sprintf
       (s, "\tautotopic  %d\tMin idle when away before auto-\177topic on return",
	me->pref.maxTopicIdle/60);
     me->Send ( s, 0, false );
      }  // topics on
#endif

     sprintf
       (s, "\tautowho    %d\tMin idle when away before auto-\177who on return",
	me->pref.maxAwayIdle/60);
     me->Send ( s, 0, false );

     sprintf
       (s, "\tdsize      %d\tNumber of sides on dice for \177roll command",
	me->pref.dice_n_sides);
     me->Send ( s, 0, false );

     sprintf
       (s, "\thereidle   %d\tMax idle time to appear in \177here listing",
	me->pref.idle_here_threshold);
     me->Send ( s, 0, false );

     sprintf
       (s, "\tjetlag     %d\tTimezone offset from server",
	me->pref.jetlag );
     me->Send ( s, 0, false );

     sprintf
       (s, "\tlocal      %s\tTelnet local echo (for screwy telnets)",
	me->pref.local_mode ? "Yes" : "No");
     me->Send ( s, 0, false );

     sprintf
       (s, "\tmsgchk     %d\tCheck interval for new mail (minutes)",
	me->pref.auto_msgchk_minutes );
     me->Send ( s, 0, false );

     if (me->is_virt) {
       me->Send
	  ("\tpassword   ***\tType \177set password to change your password",
	   0, false);
     }

#if USE_RECAP
     sprintf
       (s, "\trecap      %s\tShow recap of conversation at login",
	me->pref.show_recap ? "Yes" : "No");
     me->Send ( s, 0, false );
#endif	/* USE_RECAP */

     sprintf
       (s, "\trot10      %s\tROT10 digits when performing ROT13",
	me->pref.rot10_digits_too ? "Yes" : "No");
     me->Send ( s, 0, false );

     sprintf
       (s, "\trotamt     %d\tROT13 rotation distance", me->pref.rot_offset );
     me->Send ( s, 0, false );

     sprintf
       (s, "\tshowaidle  %s\tShow AWAY:idle users during \177showaway/\177users",
	me->pref.showaidle ? "Yes" : "No");
     me->Send ( s, 0, false );

     sprintf
       (s, "\ttabself    %s\tTab-completion includes your own username",
	me->pref.tab_expand_incl_self ? "Yes" : "No");
     me->Send ( s, 0, false );

     sprintf
       (s, "\ttrueidle   %s\tIdle time based on last keypress",
	me->pref.wants_true_idle ? "Yes" : "No");
     me->Send ( s, 0, false );

     return 0;
   }

   // Below, we check for each of the prefs, roughly ordered by reverse
   // assumed frequency of change; this is intended to save a little cpu
   // time overall, but let's face it, mostly just to avoid boredom while
   // coding this command. :)

   int retv;

   retv = _try_num_param(room, me, argc, flarg,
			"dsize", "dice number of sides",
			me->pref.dice_n_sides, s);
   if (retv != -1)
     return retv;

   retv = _try_num_param(room, me, argc, flarg,
			"hereidle", "hereidle threshold (minutes)",
			me->pref.idle_here_threshold, s);
   if (retv != -1)
     return retv;

   retv = _try_num_param(room, me, argc, flarg,
			"autowho", "autowho time (minutes)",
			me->pref.maxAwayIdle, s, 60);
   if (retv != -1)
     return retv;

   if (! me->running_cmd) {
     // only for loading settings file, not for !set command (kludge, eh.)

     retv = _try_num_param(room, me, argc, flarg,
			   "_ignoringMask", "",
			   me->pref.ignore_mask, s);
     if (retv != -1)
       return retv;
   }

#ifdef ALLOW_TOPICS
   retv = _try_num_param(room, me, argc, flarg,
			"autotopic", "autotopic time (minutes)",
			me->pref.maxTopicIdle, s, 60);
   if (retv != -1)
     return retv;
#endif

   retv = _try_num_param(room, me, argc, flarg,
			"autoaway", "auto-away time (minutes)",
			me->pref.auto_away_seconds, s, 60);
   if (retv != -1)
   {
     me->setAutoAway (me->pref.auto_away_seconds/60);
     return retv;
   }

   retv = _try_num_param(room, me, argc, flarg,
			"msgchk", "new mail check time (minutes)",
			me->pref.auto_msgchk_minutes, s);
   if (retv != -1)
   {
     if (me->pref.auto_msgchk_minutes > 0)
     {
       me->setAutoMsgchk (me->pref.auto_msgchk_minutes);  // set timer up
     }
     return retv;
   }


   retv = _try_bool_param(room, me, argc, flarg,
			  "tabself", "tab completion of own username",
			  me->pref.tab_expand_incl_self, s);
   if (retv != -1)
     return retv;

#if USE_RECAP
   retv = _try_bool_param(room, me, argc, flarg,
			  "recap", "show recap of conversation at login",
			  me->pref.show_recap, s);
   if (retv != -1)
     return retv;
#endif	/* USE_RECAP */

   retv = _try_bool_param(room, me, argc, flarg,
			  "rot10", "rot10 of digits during rot13",
			  me->pref.rot10_digits_too, s);
   if (retv != -1)
     return retv;

   retv = _try_bool_param(room, me, argc, flarg,
			  "showaidle", "show AWAY:idle users during \177showaway/\177users",
			  me->pref.showaidle, s);
   if (retv != -1)
     return retv;

   retv = _try_num_param(room, me, argc, flarg,
			  "jetlag", "timezone offset from server",
			  me->pref.jetlag, s);
   if (retv != -1)
     return retv;

   {
     int rotval = me->pref.rot_offset;
     retv = _try_num_param(room, me, argc, flarg,
			   "rotamt", "rot13 rotation distance",
			   me->pref.rot_offset, s);
     if (retv != -1)
     {
       if ((me->pref.rot_offset < 0) || (me->pref.rot_offset > 25))
	 {
	   me->pref.rot_offset = rotval;
	   if (me->running_cmd)  me->savePrefs();
	   me->Send("Must be within 0-25.", 0, false);
	   return RVAL_RETAIN_BUFFER;
	 }
       return retv;
     }
   }

   retv = _try_bool_param(room, me, argc, flarg,
			  "trueidle", "idle time based on last keypress",
			  me->pref.wants_true_idle, s);
   if (retv != -1) {
     if (me->pref.wants_true_idle) {
       me->lastkey = me->lastping;	// reset idle, otherwise it won't til
     }					// next keypress
     return retv;
   }

   retv = _try_bool_param(room, me, argc, flarg,
			  "local", "telnet local mode",
			  me->pref.local_mode, s);
   if (retv != -1) {
     if( me->pref.local_mode )
       me->uWrite( telnet_local_on );
     else
       me->uWrite( telnet_local_off );
     return retv;
   }


#if USE_VIRTUSERS
   if (0 == strncasecmp (flarg[0].strval, "password", 8))
   {
     if (! me->is_virt) {
       me->Send("Only virtual users can change their password here.", 0, true);
       return 0;
     }
     // set substate to change it (well, to prompt for change in it)
     me->setAdmSubstate(ss_pwch_getoldpw);
     return 0;
   }
#endif  /* USE_VIRTUSERS */


   if (! strcasecmp("altcmd", flarg[0].strval))
   {
     if ((argc != 2) || (flarg[1].strval[0] == '\0'))
     {
       if (me->pref.cmd_char_alt != '\0')
	 sprintf (s, "Current alternate command character is %c",
		  me->pref.cmd_char_alt);
       else
	 sprintf (s, "No alternate command character is set.");
       me->Send (s, 0, false);
       return 0;
     }

     if (flarg[1].strval[1] != '\0') {
       me->Send ("Only one character, please.", 0, false);
       return RVAL_RETAIN_BUFFER;
     }

     me->pref.cmd_char_alt = flarg[1].strval[0];

     if (me->pref.cmd_char_alt == me->pref.cmd_char)  // to un-set, set to
       me->pref.cmd_char_alt = '\0';		// same as the cmd char :)

     if (me->running_cmd) {
       if (me->pref.cmd_char_alt != '\0')
       {
	 sprintf (s, "Alternate command-prefix character set to '%c'.",
		  me->pref.cmd_char_alt);
	 me->Send (s, 0, false);
	 me->Send ("To un-set it, use \177set altcmd \177", 0, false);
       } else {
	 me->Send ("Alternate command-prefix character un-set.", 0, false);
       }
       if (me->running_cmd)  me->savePrefs();
     }
     return 0;
   }


   // trap anything other than "command"

   if (strcasecmp ("command", flarg[0].strval))
   {
     if (! me->state_is_load_prefs())
     {
       // print no error message if we're loading prefs from file,
       // just ignore it instead.
       // (forward-compatability, topics-on-off compatability)

#ifdef ALLOW_TOPICS
       if (room->topicsAreOn())
	 me->Send
	   ("Valid prefs are: command, altcmd, dsize, hereidle, autoaway, autowho, autotopic, local, trueidle.", 0, false);
       else
#endif
	 me->Send
	   ("Valid prefs are: command, altcmd, dsize, hereidle, autoaway, autowho, local, trueidle.", 0, false);
     }
     return 0;
   }

   if ((argc != 2) || (flarg[1].strval[0] == '\0')) {
     sprintf (s, "Current command escape character is %c", me->pref.cmd_char);
     me->Send (s, 0, false);
     return 0;
   }

   if (flarg[1].strval[1] != '\0') {
     me->Send ("Only one character, please.", 0, false);
     return RVAL_RETAIN_BUFFER;
   }

   me->pref.cmd_char = flarg[1].strval[0];

   if (me->running_cmd) {
     sprintf (s, "Command-prefix character set to '%c'.", me->pref.cmd_char);
     me->Send (s, 0, false);
     me->savePrefs();
   }
   return 0;
 }


// idea from dracofav 19991029.2040
int SLForumCmd::c_here
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {
  
  room->who_sort_is_valid = false;  // trigger re-sort
  room->ListUsers (me, true);
  room->who_sort_is_valid = false;  // mark sort as invalid
  return 0;
}



/** report the current version */
int SLForumCmd::c_version
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {

  me->Send (VERSION_MESSAGE, 0, false);
  room->printRoomUptime(me);
  return 0;
}


/** report the current uptime */
int SLForumCmd::c_uptime
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {

  room->printRoomUptime(me);
  return 0;
}



/** Add for timer - obviously */
/* Added by MaTTeD 20000126.1730 */

int SLForumCmd::c_timer
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg)
{

const struct tm	*ptime;
time_t		then = time(NULL);
char            s[128];

if (argc <= 0)
  {   // just check timer status; -1 is for a hacked recursive call (See below)
    if (me->timer_expire_hour == -1)
      {		// nothing is or was set
	if (argc == 0)
	  me->Send
	    ( "No timer set. Use \177timer <time-or-minutes> [public] [<description>]",
	      0, false );
      }
    else 
      {		// note the argc<0 hack for the recursive call to display
		// the current timer when setting a new one
	  int jetlag_hr = me->pref.jetlag;  
	  if (jetlag_hr < 0) jetlag_hr += 24;
	  jetlag_hr = (jetlag_hr + me->timer_expire_hour) % 24;

	sprintf (s,
		 (argc >= 0
		  ? "%stimer %s set for %d:%02d%c"
		  : "Replaces %stimer %s set for %d:%02d%c"
		  ),
		 (me->IsTimerPublic ? "public " : ""),
		 (me->IsTimerSet ? "currently" : "which was"),
		  jetlag_hr, me->timer_expire_min,
		 (me->timer_message[0] != '\0') ? ':' : '.'
		 );
	me->Send( s, 0, false );
	if (me->timer_message[0] != '\0') {
	  me->Send ( me->timer_message, 0, false );
	}
      }

      if (argc == 0) {
	  // list public timers
	  room->who_sort_is_valid = false;
	  room->ListPubTimers (me);
	  room->who_sort_is_valid = false;
      }
  } else {
    // you are setting a timer or unsetting it.

    time_t secs;

    {
      char *last;  // for strtol
      char *colon = index(flarg[0].strval, ':');
      if (colon == NULL) {
	// no colon, just minutes count
	secs = (time_t) strtol (flarg[0].strval, &last, 10);
	if ((*last != '\0') || (secs < 0) || (secs >= 60*24))
	  {
	    me->Send ("You need a number of minutes between 1 and 1439 (24*60-1).", 0, false);
	    return RVAL_RETAIN_BUFFER;
	  }

	if (secs < 0) // not valid
	  {
	    me->Send("Can't be negative.  Format: \177timer <time or mins> [public] [<reason>]", 0, false);
	    return RVAL_RETAIN_BUFFER;
	  }
	if (secs == 0) //reset timer (turn it off if it was on)
	  {
	    if (! me->IsTimerPublic)
	      me->Send( "Alarm cancelled.", 0, false );
	    else {
	      me->lastkey = me->lastping;	// reset idle time

	      snprintf (s, 127, "Public alarm from %s for %d:%02d cancelled.",
			me->name, me->timer_expire_hour, me->timer_expire_min);
	      room->Announce (s, 0, false);
	    }
	    me->IsTimerSet = false;
	    me->timer_message[0] = '\0';
	    return 0;
	  }

	// if we're here, they just entered a new time in minutes from now.
	// we'll set it below.

	// end if-simple-number processing
	
      } else {

	// has colon.  check that it has just one, and process.

	if ((NULL != index(colon+1, ':')) || (colon == flarg[0].strval)
	    || (*(colon+1) == '\0'))
	{
	  me->Send ("Time parameter must be hh:mm or just minfromnow.", 
		    0, false);
	  return RVAL_RETAIN_BUFFER;
	}
	int hour, min;
	*colon = '\0';
	char *last; // for strtol
	hour = (int) strtol (flarg[0].strval, &last, 10);
	if (((*last != '\0') && (*last != ' '))
	    || (hour < 0) || (hour > 23)) {
	  me->Send ("Hour must be between 0 and 23 inclusive.", 0, false);
	  return RVAL_RETAIN_BUFFER;
	}
	min = (int) strtol(colon+1, &last, 10);
	if (((*last != '\0') && (*last != ' '))
	    || (min < 0) || (min > 59)) {
	  me->Send ("Minute must be between 0 and 59 inclusive.", 0, false);
	  return RVAL_RETAIN_BUFFER;
	}

	// now adjust for jetlag... what we do is adjust the "current time" we
	// check below, to match this guy's timezone, and then we can properly
	// determine if it's today or tomorrow, and then how far it is (in
	// minutes) from now.

	// now to convert that absolute hh:mm to a c_time...
	struct tm *pnowtime;
	secs = time(NULL);
	secs += (60 * 60 * me->pref.jetlag);  // ADD to get his 'current' time
	pnowtime = localtime(&secs);
	if (pnowtime == NULL) {
	  room->Announce ("Assert failed in c_timer: L1344: pnowtime NULL");
	  return 0;
	}

	// adjust pnowtime to be p-then-time, by 'normalizing' each to be
	// the # of minutes after midnight (for now ignoring mm,dd,yy).
	// ("midnight" meaning the start of today.)

	int nowtime = pnowtime->tm_hour * 60 + pnowtime->tm_min;
	int thentime = hour * 60 + min;

	if (thentime <= nowtime) {
	  // it's tomorrow at an earlier time then current clock time.
	  // So, add a day's worth of minutes.
	  thentime += 60*24;
	}

	// "secs" should be time (minutes) from now til then:
	secs = thentime - nowtime;   // minutes

	// We now have the proper # minutes, as if they'd typed !timer 520 
	// or whatnot instead of !timer 11:07.
	// The # of minutes does not care about timezones, it's relative to
	// right now.  Time goes the same speed in all timezones here.
      }
    }

    // now take the # minutes from now, and translate that back to an hh:mm
    // time (local time), to keep the alarm-checking routine straightforward.
    
    secs = secs * 60;
    then += secs;   // 'then' was current GMT time when we started out here
    ptime = localtime(&then);  // convert to local hours, mins for alarm time
    if (ptime == NULL) {
      room->Announce ("Assert failed in c_timer: cmd:L1319: ptime is NULL");
      return 0;
    }

    // take what we need from the struct: the local hours, mins.
    int newhr = ptime->tm_hour;
    int newmin = ptime->tm_min;

    // display a previously set timer that's now being replaced (-1 is a hack)
    if (me->IsTimerSet)
      c_timer (room, me, -1, NULL);

    // set the new timer
    me->timer_expire_hour = newhr;
    me->timer_expire_min  = newmin;
    me->IsTimerSet = true;
    me->IsTimerPublic = false;   // might override this default below
		
    {
      // now look for the reason (if any) for the timer.
      const char *rest;
      if (argc == 1) {
	rest = NULL;
      } else {
	rest = (char*) flarg[1].strval;

	if ((0 == strncasecmp(rest, "public", 6))
	    && ( (rest[6] == ' ') || (rest[6] == '\0')))
	{
	  me->IsTimerPublic = true;
	  if (rest[6] == '\0')
	    rest = NULL;	// nothing after "public"
	  else {
	    rest = &(rest[7]);	// move to msg. past "public", eat whitespace
	    while (*rest == ' ')
	      ++rest;
	    if (*rest == '\0')
	      rest = NULL;
	  }
	}
      }

      if (rest == NULL)
	me->timer_message[0] = '\0';
      else
	strncpy(me->timer_message, rest, USERAWAYSIZ-3);

      // We're done with all those vars up above.
      // Overload "newhr" for the timer expire hour,
      // which we add jetlag to if it's a nonpublic timer
      // so the time looks right to the user.
      if (! me->IsTimerPublic)
      {
	  newhr += me->pref.jetlag;
	  if (newhr < 0)
	      newhr += 24;
	  else if (newhr > 23)
	      newhr -= 24;
      }

      sprintf (s, "%stimer set for %d:%02d%c %s",
	       (me->IsTimerPublic ? "public " : ""),
	       newhr,
	       newmin,
	       (rest != NULL ? ':' : ' '),
	       me->timer_message);

      if (! me->IsTimerPublic)
	me->Send( s, 0, false );
      else {
	me->lastkey = me->lastping;	// reset idle time

	char frombuf[USERNAMESIZ+10];
	snprintf (frombuf, sizeof(frombuf)-1, "Set by %s:", me->GetName());
	room->Announce (0, frombuf, false);	// set by
	room->Announce (0, s, false);		// time and detail msg
      }
    }
  }  // end if-{checking}-else-{setting}

 return 0;
}  // end c_timer



#ifdef ALLOW_LOCKOUT
			// jer added 20000607.1730? (check cmd.h)
int SLForumCmd::c_lockout
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg)
{
  if (me->is_guest) {
    if (argc!=1) {
      me->Send ("Reminder: Guests have no password, so they can't unlock.", 0, false);
      me->Send ("To lock anyway, type \177lockout guest", 0, false);
      return RVAL_RETAIN_BUFFER;
    } else if (strncasecmp(flarg[0].strval, "guest", 5)!=0) {
      return 0;
    }
  }
  me->lock_me();
  return 0;
}  // c_lockout

#endif  /* ALLOW_LOCKOUT */



// jer 20000610.1100
/** Manipulate the ban list */
int SLForumCmd::c_ban
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg)
{
  return i_listmanip
    (me->pref.wblist, room, me, argc, flarg,
     WBLIST_MAXSIZE, &SLForumUser::wbListAdd,
     "\r\t-- List of users banned from whispering to us: --\r\n");
}  // c_ban


int SLForumCmd::c_admin
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg)
{
  if (! me->is_admin)
    return c_help(room, me, 0, NULL);  // no such command

#ifdef ALLOW_TOPICS
  if (argc > 0)
  {
	  // only one possible param: TOPICS or TOPICS ON or TOPICS OFF
	  if (0 == strncasecmp (flarg[0].strval, "topic", 5))
	  {
		  if (argc >= 2)
		  {
			  if (0 == strncasecmp(flarg[1].strval, "off", 3))
			  {
				room->setTopicsOnOff (false);
				// clear any admin's HINT_ADMIN_TOPICS_ON bit,
				// in case it's been a while...
				for (int i=MAX_CHAT_USERS - 1; i>=0; --i)
				{
				    room->user[i]->hintbits &=
					(~ SLForumUser::HINT_ADMIN_TOPICS_ON);
				}
			  }
			  else if (0 == strncasecmp(flarg[1].strval, "on", 2))
				room->setTopicsOnOff (true);
			  else
				me->Send("Usage: \177admin topics [ON|OFF]",
					 0, false);
		  }
		  if (room->topicsAreOn())
			  me->Send ("Topics are ON", 0, false);
		  else
			  me->Send ("Topics are OFF", 0, false);
	  }
	  return 0;
  }
#endif  /* ALLOW_TOPICS */
  
  //   if (argc == 0) {

    me->setAdmSubstate(ss_mainmenu);  // interactive admin mode
    return 0;

    // }
#if 0   /* batch mode not ready for prime time yet */
  if (argc < 2)
  {
	if (argc > 0) {
	    me->Send("Not enough arguments.", 0, false);
	}
	me->Send("Admin commands: (for virtual users)", 0, false);
	me->Send("\t\177admin add   <username> <pw> <full name>", 0, false);
	me->Send("\t\177admin del   <username>", 0, false);
	me->Send("\t\177admin name  <username> <new full name>", 0, false);
	me->Send("\t\177admin pw    <username> <new pw>", 0, false);
	me->Send("\t\177admin admin <username> on or off", 0, false);
	return 0;
  }

    // parse subcmds:
    // (argument format is ?W?U?W?*)

    if (0 == strcasecmp("add", flarg[0].strval))
    {
      // make sure no virtuser or real user has that entry
      // (STATE STATE STATE should that use slf_getpwnam, or real getpwnam
      //  and then virtpwnam... and if the latter, what about NT later?)
      // exit if they do
      // use slf_virtuser_makepwentry
      // use slf_virtuser_put_info
      // report success.
      // return.
    }

    if (0 == strcasecmp("del", flarg[0].strval))
    {
      // make sure they're a virtuser
      // oops, we don't yet _have_  a del virtuser function
      // make that and call it ;)
      // report success
      // return
    }

    if (0 == strcasecmp("name", flarg[0].strval))
    {
      // make sure they're a virtuser
      // get their info
      // change real name
      // write it back out (      // use slf_virtuser_put_info)
      // report success
      // return
    }

    if (0 == strcasecmp("pw", flarg[0].strval))
    {
      // make sure they're a virtuser
      // get their info
      // generate a new pw (use slf_cryptnewpw)
      // change password
      // write it back out (      // use slf_virtuser_put_info)
      // report success
      // return
    }

    if (0 == strcasecmp("admin", flarg[0].strval))
    {
      // make sure they're a virtuser
      // make sure "on" or "off" is the last param (_try_bool?)
      // get their info
      // set admin status
      // write it back out (      // use slf_virtuser_put_info)
      // report success
      // return
    }

    me->Send ("Unknown subcommand.", 0, false);
    return RVAL_RETAIN_BUFFER;
#endif  /* 0  -- batch mode not ready for prime time yet */
}  // c_admin







// jer 20000803
int SLForumCmd::c_emote
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg) {

  // one arg, string.
  if (argc == 0) {
    me->Send ("Usage: \177me <action>", 0, false);
    return RVAL_RETAIN_BUFFER;
  }

  char buf[USERBUFSIZ];  buf[USERBUFSIZ-1] = '\0';
  char splat[13];        splat[12] = '\0';
  snprintf(splat, 12, "* %s", me->name);
  snprintf(buf, USERBUFSIZ-1, "%10s %s *", splat, flarg[0].strval);

  // make sure you can always hear your emotes,
  // even if you're ignoring emotes.
  int ignoremask = me->pref.ignore_mask;
  me->pref.ignore_mask = 0;
  // send it
  room->BroadCast(NULL, buf, (me->MSG_EMOTE | me->MSG_SYS));
  me->pref.ignore_mask = ignoremask;  // restore ignore mask

  me->lastkey = me->lastping;   // emoting is a public action; reset idle
    
  return 0;
}  // c_emote (!me)



#ifdef ALLOW_MACROS

int SLForumCmd::c_macro
   (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg)
{
  if (argc != 1)
  {
      me->Send ("Use: \177macro add or \177macro list or \177macro clear", 0, false);
      me->Send ("     or \177macro del or \177macro edit", 0, false);
  } else {
      if (0 == strcasecmp("add", flarg[0].strval))
	  me->setAdmSubstate(ss_macro_addedit_readkey);
      else if (0 == strcasecmp("edit", flarg[0].strval))
	  me->setAdmSubstate(ss_macro_addedit_readkey);
      else if (0 == strcasecmp("list", flarg[0].strval))
	  me->listCurrentMacros();
      else if (0 == strcasecmp("del", flarg[0].strval))
	  me->setAdmSubstate (ss_macro_delete_readkey);
      else if (0 == strcasecmp("clear", flarg[0].strval))
      {
	  me->clearCurrentMacros();
	  if (me->running_cmd) me->savePrefs();
      }
      else
      {
	  me->Send ("Use: \177macro add or \177macro list or \177macro clear",
		    0, false);
	  me->Send ("     or \177macro del or \177macro edit", 0, false);
      }

  }
  return 0;
}  // c_macro

#endif /* def (ALLOW_MACROS) */


#include "response-8ball.h"

int SLForumCmd::c_8ball
  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg)
{	
	char buf8ball[80];

	// Knuth recommended way, using high-order bits and avoiding % pecularities...
	int r =(int) ( ((double)  (sizeof(response8)/sizeof(char*)))*rand()
		       / (RAND_MAX+1.0));

	int ign = me->pref.ignore_mask;	// so we hear our own 8ball
	me->pref.ignore_mask = 0;
	me->lastkey = me->lastping;	// reset idle time

	{
		const char *mename = me->GetName();
		if (argc > 0) {
			buf8ball[79] = '\0';
			snprintf (buf8ball, 79, "8ball, %s", flarg[0].strval);
			room->BroadCast (mename, buf8ball, me->MSG_PUBLIC);
		}
		buf8ball[79] = '\0';
		snprintf (buf8ball, 79, "The 8-Ball has advised %s:", mename);
		room->Announce (me->MSG_8BALL | me->MSG_PUBLIC, buf8ball);
	}
	room->Announce (me->MSG_8BALL | me->MSG_PUBLIC, response8[r]);
	room->Announce ("(Type \177ignore 8ball to not hear these.)",
			me->HINT_IGN_8BALL, false);
	me->pref.ignore_mask = ign;   // restore mask after we hear our 8ball
	return 0;
}  // c_8ball



//------------------------------------------------------

//int SLForumCmd::c_proto
//  (SLForumRoom *room, SLForumUser *me, int argc, slfparam *flarg)
//{
//}  // c_proto

