/**************************************************************************** * [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; } }