/***************************************************************************** * DikuMUD (C) 1990, 1991 by: * * Sebastian Hammer, Michael Seifert, Hans Henrik Staefeldt, Tom Madsen, * * and Katja Nyboe. * *---------------------------------------------------------------------------* * MERC 2.1 (C) 1992, 1993 by: * * Michael Chastain, Michael Quan, and Mitchell Tse. * *---------------------------------------------------------------------------* * SMAUG 1.4 (C) 1994, 1995, 1996, 1998 by: Derek Snider. * * Team: Thoric, Altrag, Blodkai, Narn, Haus, Scryn, Rennard, Swordbearer, * * gorog, Grishnakh, Nivek, Tricops, and Fireblade. * *---------------------------------------------------------------------------* * SMAUG 1.7 FUSS by: Samson and others of the SMAUG community. * * Their contributions are greatly appreciated. * *---------------------------------------------------------------------------* * LoP (C) 2006, 2007, 2008 by: the LoP team. * *---------------------------------------------------------------------------* * Command interpretation module * *****************************************************************************/ #include <ctype.h> #include <stdio.h> #include <string.h> #include <time.h> #include <sys/time.h> #include "h/mud.h" /* Externals */ void refresh_page( CHAR_DATA *ch ); void subtract_times( struct timeval *etime, struct timeval *sttime ); bool check_social( CHAR_DATA *ch, const char *command, char *argument ); char *check_cmd_flags( CHAR_DATA *ch, CMDTYPE *cmd ); bool check_channel( CHAR_DATA *ch, char *command, char *argument ); /* Log-all switch. */ bool fLogAll = false; CMDTYPE *command_hash[126]; /* hash table for cmd_table */ SOCIALTYPE *social_index[126]; /* hash table for socials */ void show_similar_commands( CHAR_DATA *ch, char *command ) { CMDTYPE *cmd; int hash; short matched = 0, checked = 0, totalmatched = 0, found = 0; if( !command || command[0] == '\0' ) return; hash = LOWER( command[0] ) % 126; send_to_pager( "Similar Commands:\r\n", ch ); for( cmd = command_hash[hash]; cmd; cmd = cmd->next ) { matched = 0; if( cmd->perm > get_trust( ch ) ) continue; /* Lets check only up to 10 spots */ for( checked = 0; checked <= 10; checked++ ) { if( !cmd->name[checked] || !command[checked] ) break; if( LOWER( cmd->name[checked] ) == LOWER( command[checked] ) ) matched++; } if( ( matched > 1 && matched > ( checked / 2 ) ) || ( matched > 0 && checked < 2 ) ) { pager_printf( ch, " %-20s ", cmd->name ); if( ++found == 4 ) { found = 0; send_to_pager( "\r\n", ch ); } totalmatched++; } } if( found != 0 ) send_to_pager( "\r\n", ch ); if( totalmatched == 0 ) send_to_pager( "No similar commands found.\r\n", ch ); } /* Character not in position for command? */ bool check_pos( CHAR_DATA *ch, short position ) { if( is_npc( ch ) && ch->position > 3 ) /*Band-aid alert? -- Blod */ return true; if( ch->position < position ) { switch( ch->position ) { case POS_DEAD: send_to_char( "A little difficult to do when you're DEAD...\r\n", ch ); break; case POS_MORTAL: case POS_INCAP: send_to_char( "You're hurt far too bad for that.\r\n", ch ); break; case POS_STUNNED: send_to_char( "You're too stunned to do that.\r\n", ch ); break; case POS_SLEEPING: send_to_char( "In your dreams, or what?\r\n", ch ); break; case POS_RESTING: send_to_char( "Nah... You feel too relaxed...\r\n", ch ); break; case POS_SITTING: send_to_char( "You can't do that sitting down.\r\n", ch ); break; case POS_FIGHTING: if( position <= POS_EVASIVE ) send_to_char( "This fighting style is too demanding for that!\r\n", ch ); else send_to_char( "No way! You're still fighting!\r\n", ch ); break; case POS_DEFENSIVE: if( position <= POS_EVASIVE ) send_to_char( "This fighting style is too demanding for that!\r\n", ch ); else send_to_char( "No way! You're still fighting!\r\n", ch ); break; case POS_AGGRESSIVE: if( position <= POS_EVASIVE ) send_to_char( "This fighting style is too demanding for that!\r\n", ch ); else send_to_char( "No way! You're still fighting!\r\n", ch ); break; case POS_BERSERK: if( position <= POS_EVASIVE ) send_to_char( "This fighting style is too demanding for that!\r\n", ch ); else send_to_char( "No way! You're still fighting!\r\n", ch ); break; case POS_EVASIVE: send_to_char( "No way! You're still fighting!\r\n", ch ); break; } return false; } return true; } extern char lastplayercmd[MIL * 2]; /* * The main entry point for executing commands. * Can be recursively called from 'at', 'order', 'force'. */ void interpret( CHAR_DATA *ch, char *argument ) { char command[MIL], logline[MIL], logname[MIL], log_buf[MSL], *buf; TIMER *timer = NULL; CMDTYPE *cmd = NULL; int trust, loglvl; bool found; struct timeval time_used; long tmptime; if( !ch ) { bug( "%s: null ch!", __FUNCTION__ ); return; } if( !ch->in_room ) { bug( "%s: null in_room!", __FUNCTION__ ); return; } found = false; if( ch->substate == SUB_REPEATCMD ) { DO_FUN *fun; if( !( fun = ch->last_cmd ) ) { ch->substate = SUB_NONE; bug( "%s: SUB_REPEATCMD with NULL last_cmd", __FUNCTION__ ); return; } else { int x; /* * yes... we lose out on the hashing speediness here... * but the only REPEATCMDS are wizcommands (currently) */ for( x = 0; x < 126; x++ ) { for( cmd = command_hash[x]; cmd; cmd = cmd->next ) { if( cmd->do_fun == fun ) { found = true; break; } } if( found ) break; } if( !found ) { cmd = NULL; bug( "%s: SUB_REPEATCMD: last_cmd invalid", __FUNCTION__ ); return; } snprintf( logline, sizeof( logline ), "(%s) %s", cmd->name, argument ); } } if( !cmd ) { /* Changed the order of these ifchecks to prevent crashing. */ if( !argument || argument[0] == '\0' ) { bug( "%s: null argument!", __FUNCTION__ ); return; } /* Strip leading spaces. */ while( isspace( *argument ) ) argument++; if( !argument || argument[0] == '\0' ) return; /* Implement freeze command. */ if( !is_npc( ch ) && xIS_SET( ch->act, PLR_FREEZE ) ) { send_to_char( "You're totally frozen!\r\n", ch ); return; } /* * Grab the command word. * Special parsing so ' can be a command, also no spaces needed after punctuation. */ mudstrlcpy( logline, argument, sizeof( logline ) ); 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 = get_trust( ch ); for( cmd = command_hash[LOWER( command[0] ) % 126]; cmd; cmd = cmd->next ) { if( !str_prefix( command, cmd->name ) && ( cmd->perm <= trust || ( !is_npc( ch ) && ch->pcdata->council && is_name( cmd->name, ch->pcdata->council->powers ) && cmd->perm <= ( trust + MAX_CPD ) ) || ( !is_npc( ch ) && ch->pcdata->bestowments && ch->pcdata->bestowments[0] != '\0' && is_name( cmd->name, ch->pcdata->bestowments ) && cmd->perm <= ( trust + sysdata.bestow_dif ) ) ) ) { found = true; break; } } /* Turn off afk bit when any command performed. */ if( !is_npc( ch ) && xIS_SET( ch->act, PLR_AFK ) && ( str_cmp( command, "AFK" ) ) ) { xREMOVE_BIT( ch->act, PLR_AFK ); act( AT_GRAY, "$n is no longer afk.", ch, NULL, NULL, TO_CANSEE ); #ifdef IMC if( IMCIS_SET( IMCFLAG( ch ), IMC_AFK ) ) { send_to_char( "You're no longer AFK to IMC2.\r\n", ch ); IMCREMOVE_BIT( IMCFLAG( ch ), IMC_AFK ); } #endif } } /* Log and snoop. */ snprintf( lastplayercmd, sizeof( lastplayercmd ), "%s used %s", ch->name, logline ); if( found && cmd->log == LOG_NEVER ) mudstrlcpy( logline, "XXXXXXXX XXXXXXXX XXXXXXXX", sizeof( logline ) ); loglvl = found ? cmd->log : LOG_NORMAL; if( ( ch->logged ) || fLogAll || loglvl == LOG_BUILD || loglvl == LOG_HIGH || loglvl == LOG_ALWAYS ) { /* Added by Narn to show who is switched into a mob that executes a logged command. */ snprintf( log_buf, sizeof( log_buf ), "Log %s: %s", ch->name, 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 && loglvl == LOG_NORMAL && ( !ch->logged ) ) loglvl = LOG_ALL; log_string_plus( log_buf, loglvl, URANGE( 0, ( get_trust( ch ) + 1 ), PERM_MAX ) ); } if( ch->desc && ch->desc->snoop_by ) { snprintf( logname, sizeof( logname ), "%s", ch->name ); write_to_buffer( ch->desc->snoop_by, logname, 0 ); write_to_buffer( ch->desc->snoop_by, "% ", 2 ); write_to_buffer( ch->desc->snoop_by, logline, 0 ); write_to_buffer( ch->desc->snoop_by, "\r\n", 2 ); } /* check for a timer delayed command (search, dig, detrap, etc) */ if( ( timer = get_timerptr( ch, TIMER_DO_FUN ) ) ) { int tempsub; tempsub = ch->substate; ch->substate = SUB_TIMER_DO_ABORT; ( timer->do_fun ) ( ch, (char *)"" ); if( char_died( ch ) ) return; if( ch->substate != SUB_TIMER_CANT_ABORT ) { ch->substate = tempsub; extract_timer( ch, timer ); } else { ch->substate = tempsub; return; } } /* Look for command in skill and socials table. */ if( !found ) { if( !check_skill( ch, command, argument ) && !check_social( ch, command, argument ) && !check_channel( ch, command, argument ) #ifdef IMC && !imc_command_hook( ch, command, argument ) #endif ) { EXIT_DATA *pexit; /* check for an auto-matic exit command */ if( ( pexit = find_door( ch, command, true ) ) && xIS_SET( pexit->exit_info, EX_xAUTO ) ) { if( xIS_SET( pexit->exit_info, EX_CLOSED ) && ( !IS_AFFECTED( ch, AFF_PASS_DOOR ) || xIS_SET( pexit->exit_info, EX_NOPASSDOOR ) ) ) { if( !xIS_SET( pexit->exit_info, EX_SECRET ) ) act( AT_PLAIN, "The $d is closed.", ch, NULL, pexit->keyword, TO_CHAR ); else send_to_char( "You can't do that here.\r\n", ch ); return; } move_char( ch, pexit, 0 ); return; } show_similar_commands( ch, command ); } return; } /* Character not in position for command? */ if( !check_pos( ch, cmd->position ) ) return; /* * So we can check commands for things like Polymorph * But still keep the online editing ability. -- Shaddai * Send back the message to print out, so we have the option * this function might be usefull elsewhere. Also using the * send_to_char so we can colorize the strings if need be. --Shaddai */ if( ( buf = check_cmd_flags( ch, cmd ) ) && buf[0] != '\0' ) { send_to_char( buf, ch ); return; } if( xIS_SET( cmd->flags, CMD_FULL_NAME ) && str_cmp( command, cmd->name ) ) { send_to_char( "You have to use the full name for that command to work.\r\n", ch ); return; } /* Only allow tilde's if the command is set to */ if( !xIS_SET( cmd->flags, CMD_FLAG_ALLOW_TILDE ) ) smash_tilde( argument ); /* 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 ) { log_printf_plus( LOG_NORMAL, get_trust( ch ), "[*****] LAG: %s: %s %s (R:%d S:%ld.%06ld)", ch->name, cmd->name, ( cmd->log == LOG_NEVER ? "XXX" : argument ), ch->in_room ? ch->in_room->vnum : 0, time_used.tv_sec, time_used.tv_usec ); cmd->lag_count++; /* count the lag flags */ } tail_chain( ); } /* Return true if an argument is completely numeric. */ bool is_number( char *arg ) { bool first = true; if( !arg || arg[0] == '\0' ) return false; for( ; *arg != '\0'; arg++ ) { if( first && *arg == '-' ) { first = false; continue; } if( !isdigit( *arg ) ) return false; first = 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( char *argument, char *arg_first ) { char cEnd; short count; count = 0; if( !argument || argument[0] == '\0' ) { arg_first[0] = '\0'; return (char *)"\0"; } while( isspace( *argument ) ) argument++; cEnd = ' '; if( *argument == '\'' || *argument == '"' ) cEnd = *argument++; while( *argument != '\0' || ++count >= 255 ) { if( *argument == cEnd ) { argument++; break; } if( *argument == '\n' ) break; *arg_first = *argument; arg_first++; argument++; } *arg_first = '\0'; while( isspace( *argument ) ) argument++; return argument; } /* * Pick off one argument from a string and return the rest. * Understands quotes. Delimiters = { ' ', '-' } */ char *one_argument2( char *argument, char *arg_first ) { char cEnd; short count; count = 0; if( !argument || argument[0] == '\0' ) { arg_first[0] = '\0'; return argument; } while( isspace( *argument ) ) argument++; cEnd = ' '; if( *argument == '\'' || *argument == '"' ) cEnd = *argument++; while( *argument != '\0' || ++count >= 255 ) { if( *argument == cEnd || *argument == '-' ) { argument++; break; } *arg_first = *argument; arg_first++; argument++; } *arg_first = '\0'; while( isspace( *argument ) ) argument++; return argument; } CMDF( do_timecmd ) { struct timeval sttime; struct timeval etime; static bool timing; extern CHAR_DATA *timechar; char arg[MIL]; send_to_char( "Timing\r\n", ch ); if( timing ) return; one_argument( argument, arg ); if( !arg || arg[0] == '\0' ) { send_to_char( "No command to time.\r\n", ch ); return; } if( !str_cmp( arg, "update" ) ) { if( timechar ) send_to_char( "Another person is already timing updates.\r\n", ch ); else { timechar = ch; send_to_char( "Setting up to record next update loop.\r\n", ch ); } return; } set_char_color( AT_PLAIN, ch ); send_to_char( "Starting timer.\r\n", ch ); timing = true; gettimeofday( &sttime, NULL ); interpret( ch, argument ); gettimeofday( &etime, NULL ); timing = false; set_char_color( AT_PLAIN, ch ); send_to_char( "Timing complete.\r\n", ch ); subtract_times( &etime, &sttime ); ch_printf( ch, "Timing took %ld.%06ld seconds.\r\n", etime.tv_sec, etime.tv_usec ); } void start_timer( struct timeval *sttime ) { if( !sttime ) { bug( "%s: NULL sttime.", __FUNCTION__ ); return; } gettimeofday( sttime, NULL ); } time_t end_timer( struct timeval * sttime ) { struct timeval etime; /* Mark etime before checking stime, so that we get a better reading.. */ gettimeofday( &etime, NULL ); if( !sttime || ( !sttime->tv_sec && !sttime->tv_usec ) ) { bug( "%s: bad sttime.", __FUNCTION__ ); return 0; } subtract_times( &etime, sttime ); /* sttime becomes time used */ *sttime = etime; return ( etime.tv_sec * 1000000 ) + etime.tv_usec; } void send_timer( struct timerset *vtime, CHAR_DATA *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_printf( ch, "Has been used %d times this boot.\r\n", vtime->num_uses ); ch_printf( ch, "Time (in secs): min %ld.%06ld; avg: %ld.%06ld; max %ld.%06ld" "\r\n", 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, struct 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; } } /* * This function checks the command against the command flags to make * sure they can use the command online. This allows the commands to be * edited online to allow or disallow certain situations. May be an idea * to rework this so we can edit the message sent back online, as well as * maybe a crude parsing language so we can add in new checks online without * haveing to hard-code them in. -- Shaddai August 25, 1997 */ /* Needed a global here */ char cmd_flag_buf[MSL]; char *check_cmd_flags( CHAR_DATA *ch, CMDTYPE *cmd ) { if( ch->morph && xIS_SET( cmd->flags, CMD_FLAG_POLYMORPHED ) ) snprintf( cmd_flag_buf, sizeof( cmd_flag_buf ), "You can't %s while you're polymorphed!\r\n", cmd->name ); else if( xIS_SET( cmd->flags, CMD_FLAG_NPC ) && ( !is_npc( ch ) || ch->desc || IS_AFFECTED( ch, AFF_CHARM ) ) ) snprintf( cmd_flag_buf, sizeof( cmd_flag_buf ), "You can't use %s unless your an uncharmed and unswitched npc!\r\n", cmd->name ); else if( xIS_SET( cmd->flags, CMD_FLAG_PC ) && ( is_npc( ch ) || !ch->desc ) ) snprintf( cmd_flag_buf, sizeof( cmd_flag_buf ), "You can't use %s unless your a pc with a desc!\r\n", cmd->name ); else cmd_flag_buf[0] = '\0'; return cmd_flag_buf; }