/****************************************************************************
* [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame | *
* -----------------------------------------------------------| \\._.// *
* SmaugWiz (C) 1998 by Russ Pillsbury (Windows NT version) | (0...0) *
* -----------------------------------------------------------| ).:.( *
* SMAUG (C) 1994, 1995, 1996 by Derek Snider | {o o} *
* -----------------------------------------------------------| / ' ' \ *
* SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus, |~'~.VxvxV.~'~*
* Scryn, Swordbearer, Rennard, Tricops, and Gorog. | *
* ------------------------------------------------------------------------ *
* Merc 2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael *
* Chastain, Michael Quan, and Mitchell Tse. *
* Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer, *
* Michael Seifert, Hans Henrik Staerfeldt, Tom Madsen, and Katja Nyboe. *
* ------------------------------------------------------------------------ *
* Command interpretation module *
****************************************************************************/
#include "stdafx.h"
#include "smaug.h"
#include "social.h"
#include "commands.h"
#include "mobiles.h"
#include "objects.h"
#include "rooms.h"
#include "Exits.h"
#include "SmaugWizDoc.h"
#include "descriptor.h"
#include "character.h"
#include "smaugx.h"
// Externals
void refresh_page (CCharacter *ch);
void subtract_times (struct timeval *etime, struct timeval *stime);
void DoLogging (CCharacter& Ch, const char* logline, CCmdType& Cmd,
BOOL bFound);
// Log-all switch.
BOOL fLogAll = FALSE;
// Character not in position for command?
BOOL check_pos (CCharacter *ch, short position)
{
if (ch->IsNpc () && ch->GetPosition () > POS_STUNNED)
return TRUE; // Band-aid alert? -- Blod
if (ch->GetPosition () < position) {
switch (ch->GetPosition ()) {
case POS_DEAD:
ch->SendText ("A little difficult to do when you are DEAD...\n\r");
break;
case POS_MORTAL:
case POS_INCAP:
ch->SendText ("You are hurt far too bad for that.\n\r");
break;
case POS_STUNNED:
ch->SendText ("You are too stunned to do that.\n\r");
break;
case POS_SLEEPING:
ch->SendText ("In your dreams, or what?\n\r");
break;
case POS_RESTING:
ch->SendText ("Nah... You feel too relaxed...\n\r");
break;
case POS_SITTING:
ch->SendText ("You can't do that sitting down.\n\r");
break;
case POS_FIGHTING:
case POS_DEFENSIVE:
case POS_AGGRESSIVE:
case POS_BERSERK:
if (position <= POS_EVASIVE)
ch->SendText ("This fighting style is too demanding for that!\n\r");
else
ch->SendText ("No way! You are still fighting!\n\r");
break;
case POS_EVASIVE:
ch->SendText ("No way! You are still fighting!\n\r");
break;
}
return FALSE;
}
return TRUE;
}
extern char lastplayercmd [MAX_INPUT_LENGTH*2];
// The main entry point for executing commands.
// Can be recursively called from 'at', 'order', 'force'.
void interpret (CCharacter *ch, char *argument)
{
char command [MAX_INPUT_LENGTH];
char logline [MAX_INPUT_LENGTH];
char logname [MAX_INPUT_LENGTH];
CTimerData *timer = NULL;
CCmdType *cmd = NULL;
int trust;
BOOL found;
struct timeval time_used;
long tmptime;
ASSERT (ch);
try {
found = FALSE;
logline [0] = 0;
if (ch->GetSubstate () == SUB_REPEATCMD) {
DO_FUN *fun;
if ((fun = ch->last_cmd) == NULL) {
ch->SetSubstate (SUB_NONE);
bug ("interpret: SUB_REPEATCMD with NULL last_cmd", 0);
return;
} else {
int x;
// yes... we lose out on the hashing speediness here...
// but the only REPEATCMDS are wizcommands (currently)
for (x = 0; x < MAX_COMMANDS; ++x) {
for (cmd = CommandTable.GetCommand (x); cmd;
cmd = cmd->GetNext ())
if (cmd->do_fun == fun) {
found = TRUE;
break;
}
if (found)
break;
}
if (!found) {
cmd = NULL;
bug ("interpret: SUB_REPEATCMD: last_cmd invalid");
return;
}
sprintf (logline, "(%s) %s", cmd->GetName (), argument);
}
}
if (! cmd) {
if (! argument || *argument == 0) {
bug ("interpret: null argument!");
return;
}
// Strip leading spaces.
while (isspace (*argument))
argument++;
if (argument [0] == 0)
return;
timer = get_timerptr (ch, TIMER_DO_FUN);
// REMOVE_BIT (ch->affected_by, AFF_HIDE);
// Implement freeze command.
if (! ch->IsNpc () && ch->IsFrozen ()) {
ch->SendText ("You're totally frozen!\n\r");
return;
}
// Grab the command word.
// Special parsing so ' can be a command,
// also no spaces needed after punctuation.
strcpy (logline, argument);
if (! isalpha (argument [0]) && ! isdigit (argument [0])) {
command [0] = argument [0];
command [1] = '\0';
argument++;
while (isspace (*argument))
++argument;
}
else argument = one_argument (argument, command);
// Look for command in command table.
// Check for council powers and/or bestowments
trust = ch->GetTrustLevel ();
int hash = CommandTable.GetHash (command [0]);
for (cmd = CommandTable.GetCommand (hash); cmd; cmd = cmd->GetNext ())
if (! str_prefix (command, cmd->GetName ())
&& (cmd->level <= trust
|| (!ch->IsNpc () && ch->GetPcData ()->council
&& is_name (cmd->GetName (), ch->GetPcData ()->council->powers)
&& cmd->level <= (trust+MAX_CPD))
|| (!ch->IsNpc () && ch->GetPcData ()->HasBestowments ()
&& is_name (cmd->GetName (), ch->GetPcData ()->GetBestowments ())
&& cmd->level <= (trust+5)))) {
found = TRUE;
break;
}
// Turn off afk bit when any command performed.
if (ch->IsAction (PLR_AFK) && (str_cmp (command, "AFK"))) {
ch->ClrActBit (PLR_AFK);
act (AT_GREY, "$n is no longer afk.", ch, NULL, NULL, TO_ROOM);
}
}
sprintf (lastplayercmd, "** %s: %s", ch->GetName (), logline);
// Log and snoop.
DoLogging (*ch, logline, *cmd, found);
if (ch->GetDesc () && ch->GetDesc ()->m_pSnoopBy) {
sprintf (logname, "%s", ch->GetName ());
ch->GetDesc ()->m_pSnoopBy->WriteToBuffer (logname);
ch->GetDesc ()->m_pSnoopBy->WriteToBuffer ("% ", 2);
ch->GetDesc ()->m_pSnoopBy->WriteToBuffer (logline);
ch->GetDesc ()->m_pSnoopBy->WriteToBuffer ("\n\r", 2);
}
// Handle the build interface stuff
if (ch->IsMenuActive ())
if (ch->EditMenu (argument, command))
return;
if (timer) {
int tempsub;
tempsub = ch->GetSubstate ();
ch->SetSubstate (SUB_TIMER_DO_ABORT);
timer->DoFun (ch, "");
if (char_died (ch))
return;
if (ch->GetSubstate () != SUB_TIMER_CANT_ABORT) {
ch->SetSubstate (tempsub);
extract_timer (ch, timer);
} else {
ch->SetSubstate (tempsub);
return;
}
}
// Look for command in skill and socials table.
if (!found) {
if (!check_skill (ch, command, argument)
&& !check_social (ch, command, argument)) {
CExitData *pexit;
// check for an automatic exit command
if ((pexit = find_door (ch, command, TRUE)) != NULL
&& pexit->CanAuto ()) {
if (pexit->IsClosed ()
&& (! ch->CanPass ()
|| pexit->IsNoPass ())) {
if (! pexit->IsSecret ())
act (AT_PLAIN, "The $d is closed.", ch, NULL,
pexit->keyword, TO_CHAR);
else
ch->SendText ("You cannot do that here.\n\r");
return;
}
move_char (ch, pexit, 0);
return;
}
ch->SendText ("Huh?\n\r");
}
return;
}
// Character not in position for command?
if (!check_pos (ch, cmd->position))
return;
// Berserk check for flee.. maybe add drunk to this?.. but too much
// hardcoding is annoying.. -- Altrag
if (!str_cmp (cmd->GetName (), "flee") && ch->IsBeserk ()) {
ch->SendText ("You aren't thinking very clearly..\n\r");
return;
}
// Dispatch the command.
ch->prev_cmd = ch->last_cmd; // haus, for automapping
ch->last_cmd = cmd->do_fun;
start_timer (&time_used);
(*cmd->do_fun) (ch, argument);
end_timer (&time_used);
// Update the record of how many times this command has been used (haus)
update_userec (&time_used, &cmd->userec);
tmptime = UMIN (time_used.tv_sec,19) * 1000000 + time_used.tv_usec;
// laggy command notice: command took longer than 1.5 seconds
if (tmptime > 1500000) {
sprintf (log_buf, "[*****] LAG: %s: %s %s (R:%d S:%ld.%06ld)",
ch->GetName (), cmd->GetName (),
(cmd->log == LOG_NEVER ? "XXX" : argument),
ch->GetInRoom () ? ch->GetInRoom ()->vnum : 0,
time_used.tv_sec, time_used.tv_usec);
gpDoc->LogString (log_buf, LOG_BUG, ch->GetTrustLevel ());
}
}
catch (CException* ex) {
CSwException sx;
char buf [128];
UINT id;
if (ex->GetErrorMessage (buf, sizeof (buf), &id))
sx.Printf ("CException %u:%s in Interp (%s:%s).", id, buf,
ch->GetName (), logline [0] ? logline : argument);
else sx.Printf ("Unknown CException in Interp (%s:%s).",
ch->GetName (), logline [0] ? logline : argument);
ex->Delete ();
throw sx;
}
}
void DoLogging (CCharacter& Ch, const char* logline, CCmdType& Cmd, BOOL bFound)
{
if (bFound && Cmd.log == LOG_NEVER) then return;
LogTypes LogType = bFound ? (LogTypes) Cmd.log : LOG_NORMAL;
if ((! Ch.IsNpc () && Ch.IsLogged ()) || fLogAll || LogType == LOG_BUILD
|| LogType == LOG_HIGH || LogType == LOG_ALWAYS) {
// Added by Narn to show who is switched into a mob that executes
// a logged command. Check for descriptor in case force is used.
if (Ch.GetDesc () && Ch.GetDesc ()->m_pOriginal)
sprintf (log_buf, "Log %s (%s): %s", Ch.GetName (),
Ch.GetDesc ()->m_pOriginal->GetName (), logline);
else
sprintf (log_buf, "Log %s: %s", Ch.GetName (), logline);
// Make it so a 'log all' will send most output to the log
// file only, and not spam the log channel to death -Thoric
if (fLogAll && LogType == LOG_NORMAL
&& (Ch.IsNpc () || ! Ch.IsLogged ()))
LogType = LOG_ALL;
gpDoc->LogString (log_buf, LogType, Ch.GetTrustLevel ());
}
}
BOOL check_social (CCharacter *ch, char *command, const char *argument)
{
char arg [MAX_INPUT_LENGTH];
CSocialType *social;
if ((social = SocialTable.Find (command)) == NULL)
return FALSE;
if (!ch->IsNpc () && ch->IsNoEmote ()) {
ch->SendText ("You are anti-social!\n\r");
return TRUE;
}
switch (ch->GetPosition ()) {
case POS_DEAD:
ch->SendText ("Lie still; you are DEAD.\n\r");
return TRUE;
case POS_INCAP:
case POS_MORTAL:
ch->SendText ("You are hurt far too bad for that.\n\r");
return TRUE;
case POS_STUNNED:
ch->SendText ("You are too stunned to do that.\n\r");
return TRUE;
case POS_SLEEPING:
// I just know this is the path to a 12" 'if' statement. : (
// But two players asked for it already! -- Furey
if (!str_cmp (social->GetName (), "snore"))
break;
ch->SendText ("In your dreams, or what?\n\r");
return TRUE;
}
// Search room for chars ignoring social sender and
// remove them from the room until social has been completed
CPtrList List;
CCharacter *victim = ch->GetInRoom ()->first_person;
while (victim) {
CCharacter *pNext = victim->GetNextInRoom ();
if (victim->IsIgnoring (ch)) {
if (ch->IsMortal ()
|| victim->GetTrustLevel () > ch->GetTrustLevel ()) {
victim->RemoveFromRoom ();
List.AddTail (victim);
} else {
set_char_color (AT_IGNORE, victim);
victim->SendTextf (
"You attempt to ignore %s, but are unable to do so.\n\r",
ch->GetName ());
}
}
victim = pNext;
}
one_argument (argument, arg);
victim = NULL;
if (arg[0] == '\0') {
act (AT_SOCIAL, social->others_no_arg, ch, NULL, victim, TO_ROOM);
act (AT_SOCIAL, social->char_no_arg, ch, NULL, victim, TO_CHAR);
}
else if ((victim = get_char_room (ch, arg)) == NULL) {
// If they aren't in the room, they may be in the list of
// people ignoring...
BOOL bFound = FALSE;
POSITION pos = List.GetHeadPosition ();
while (pos) {
victim = (CCharacter*) List.GetNext (pos);
if (nifty_is_name (victim->GetName (), arg) ||
nifty_is_name_prefix (arg, victim->GetName ())) {
set_char_color (AT_IGNORE, ch);
ch->SendTextf ("%s is ignoring you.\n\r", victim->GetName ());
bFound = TRUE;
break;
}
}
if (! bFound)
ch->SendText ("They aren't here.\n\r");
}
else if (victim == ch) {
act (AT_SOCIAL, social->others_auto, ch, NULL, victim, TO_ROOM);
act (AT_SOCIAL, social->char_auto, ch, NULL, victim, TO_CHAR);
} else {
act (AT_SOCIAL, social->others_found, ch, NULL, victim, TO_NOTVICT);
act (AT_SOCIAL, social->char_found, ch, NULL, victim, TO_CHAR);
act (AT_SOCIAL, social->vict_found, ch, NULL, victim, TO_VICT);
if (! ch->IsNpc () && victim->IsNpc ()
&& ! victim->IsCharmed ()
&& victim->IsAwake ()
&& ! victim->GetMobIndex ()->m_Progtypes.IsSet (ACT_PROG)) {
switch (number_bits (4)) {
case 0:
if (! ch->GetInRoom ()->IsSafe () && ch->IsEvil ())
multi_hit (victim, ch, TYPE_UNDEFINED);
else if (ch->IsNeutral ()) {
act (AT_ACTION, "$n slaps $N.", victim, NULL, ch, TO_NOTVICT);
act (AT_ACTION, "You slap $N.", victim, NULL, ch, TO_CHAR);
act (AT_ACTION, "$n slaps you.", victim, NULL, ch, TO_VICT);
} else {
act (AT_ACTION, "$n acts like $N doesn't even exist.",
victim, NULL, ch, TO_NOTVICT);
act (AT_ACTION, "You just ignore $N.", victim, NULL, ch,
TO_CHAR);
act (AT_ACTION, "$n appears to be ignoring you.",
victim, NULL, ch, TO_VICT);
}
break;
case 1: case 2: case 3: case 4:
case 5: case 6: case 7: case 8:
act (AT_SOCIAL, social->others_found, victim, NULL, ch, TO_NOTVICT);
act (AT_SOCIAL, social->char_found, victim, NULL, ch, TO_CHAR);
act (AT_SOCIAL, social->vict_found, victim, NULL, ch, TO_VICT);
break;
case 9: case 10: case 11: case 12:
act (AT_ACTION, "$n slaps $N.", victim, NULL, ch, TO_NOTVICT);
act (AT_ACTION, "You slap $N.", victim, NULL, ch, TO_CHAR);
act (AT_ACTION, "$n slaps you.", victim, NULL, ch, TO_VICT);
break;
}
}
}
// Replace the chars in the ignoring list to the room
// note that the ordering of the players in the room might change
while (! List.IsEmpty ()) {
victim = (CCharacter*) List.RemoveHead ();
victim->SendToRoom (ch->GetInRoom ());
}
return TRUE;
}
// Return true if an argument is completely numeric.
BOOL is_number (char *arg)
{
if (*arg == '\0')
return FALSE;
for (; *arg != '\0'; arg++) {
if (!isdigit (*arg))
return FALSE;
}
return TRUE;
}
// Given a string like 14.foo, return 14 and 'foo'
int number_argument (char *argument, char *arg)
{
char *pdot;
int number;
for (pdot = argument; *pdot != '\0'; pdot++) {
if (*pdot == '.') {
*pdot = '\0';
number = atoi (argument);
*pdot = '.';
strcpy (arg, pdot+1);
return number;
}
}
strcpy (arg, argument);
return 1;
}
// Pick off one argument from a string and return the rest.
// Understands quotes.
char *one_argument (const char *argument, char *arg_first)
{
char cEnd, c;
short count;
count = 0;
while (isspace (*argument))
argument++;
cEnd = ' ';
if (*argument == '\'' || *argument == '"')
cEnd = *argument++;
while ((c = *argument) != 0 || ++count >= 255) {
if (c == cEnd) {
++argument;
break;
}
if (c == '&' && IsColor (argument))
argument += 2;
else {
*arg_first = LOWER (c);
++arg_first;
++argument;
}
}
*arg_first = 0;
while (isspace (*argument))
++argument;
return NCCP argument;
}
// Pick off one argument from a string and return the rest.
// Understands quotes. Delimiters = { ' ', '-' }
char *one_argument2 (const char *argument, char *arg_first)
{
char cEnd, c;
short count;
count = 0;
while (isspace (*argument))
argument++;
cEnd = ' ';
if (*argument == '\'' || *argument == '"')
cEnd = *argument++;
while ((c = *argument) != 0 || ++count >= 255) {
if (c == cEnd || c == '-') {
++argument;
break;
}
if (c == '&' && IsColor (argument))
argument += 2;
else {
*arg_first = LOWER (c);
++arg_first;
++argument;
}
}
*arg_first = 0;
while (isspace (*argument))
++argument;
return NCCP argument;
}
void do_timecmd (CCharacter *ch, char *argument)
{
struct timeval stime;
struct timeval etime;
static BOOL timing;
extern CCharacter *timechar;
char arg [MAX_INPUT_LENGTH];
ch->SendText ("Timing\n\r");
if (timing)
return;
one_argument (argument, arg);
if (!*arg) {
ch->SendText ("No command to time.\n\r");
return;
}
if (!str_cmp (arg, "update")) {
if (timechar)
ch->SendText ("Another person is already timing updates.\n\r");
else {
timechar = ch;
ch->SendText ("Setting up to record next update loop.\n\r");
}
return;
}
set_char_color (AT_PLAIN, ch);
ch->SendText ("Starting timer.\n\r");
timing = TRUE;
gpDoc->gettimeofday (&stime, NULL);
interpret (ch, argument);
gpDoc->gettimeofday (&etime, NULL);
timing = FALSE;
set_char_color (AT_PLAIN, ch);
ch->SendText ("Timing complete.\n\r");
subtract_times (&etime, &stime);
ch->SendTextf ("Timing took %d.%06d seconds.\n\r",
etime.tv_sec, etime.tv_usec);
}
void start_timer (struct timeval *stime)
{
if (!stime) {
bug ("Start_timer: NULL stime.");
return;
}
gpDoc->gettimeofday (stime, NULL);
}
time_t end_timer (struct timeval *stime)
{
struct timeval etime;
// Mark etime before checking stime, so that we get a better reading..
gpDoc->gettimeofday (&etime, NULL);
if (!stime || (!stime->tv_sec && !stime->tv_usec)) {
bug ("End_timer: bad stime.", 0);
return 0;
}
subtract_times (&etime, stime);
// stime becomes time used
*stime = etime;
return (etime.tv_sec*1000000)+etime.tv_usec;
}
void send_timer (struct timerset *vtime, CCharacter *ch)
{
struct timeval ntime;
int carry;
if (vtime->num_uses == 0)
return;
ntime.tv_sec = vtime->total_time.tv_sec / vtime->num_uses;
carry = (vtime->total_time.tv_sec % vtime->num_uses) * 1000000;
ntime.tv_usec = (vtime->total_time.tv_usec + carry) / vtime->num_uses;
ch->SendTextf ("Has been used %d times this boot.\n\r", vtime->num_uses);
ch->SendTextf ("Time (in secs): min %d.%0.6d; avg: %d.%0.6d; "
"max %d.%0.6d\n\r", vtime->min_time.tv_sec, vtime->min_time.tv_usec,
ntime.tv_sec, ntime.tv_usec, vtime->max_time.tv_sec,
vtime->max_time.tv_usec);
}
void update_userec (struct timeval *time_used, timerset *userec)
{
userec->num_uses++;
if (!timerisset (&userec->min_time)
|| timercmp (time_used, &userec->min_time, <)) {
userec->min_time.tv_sec = time_used->tv_sec;
userec->min_time.tv_usec = time_used->tv_usec;
}
if (!timerisset (&userec->max_time)
|| timercmp (time_used, &userec->max_time, >)) {
userec->max_time.tv_sec = time_used->tv_sec;
userec->max_time.tv_usec = time_used->tv_usec;
}
userec->total_time.tv_sec += time_used->tv_sec;
userec->total_time.tv_usec += time_used->tv_usec;
while (userec->total_time.tv_usec >= 1000000) {
userec->total_time.tv_sec++;
userec->total_time.tv_usec -= 1000000;
}
}