/***************************************************************************** * 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. * *****************************************************************************/ #include <ctype.h> #include <stdio.h> #include <string.h> #include <sys/stat.h> #include "h/mud.h" int top_help; char *help_greeting; char *un_fix_help( char *text ); typedef struct help_data HELP_DATA; struct help_data { HELP_DATA *next, *prev; char *keyword; char *text; time_t updated; char *updated_by; short perm; bool nosave; }; HELP_DATA *first_help, *last_help; HELP_DATA *get_help( CHAR_DATA *ch, char *argument ); void free_help( HELP_DATA *pHelp ) { STRFREE( pHelp->text ); STRFREE( pHelp->keyword ); STRFREE( pHelp->updated_by ); DISPOSE( pHelp ); top_help--; } void free_helps( void ) { HELP_DATA *pHelp, *pHelp_next; for( pHelp = first_help; pHelp; pHelp = pHelp_next ) { pHelp_next = pHelp->next; UNLINK( pHelp, first_help, last_help, next, prev ); free_help( pHelp ); } } /* * Adds a help page to the list if it is not a duplicate of an existing page. * Page is insert-sorted by keyword. -Thoric * (The reason for sorting is to keep do_hlist looking nice) */ void add_help( HELP_DATA *pHelp ) { HELP_DATA *tHelp; int match; for( tHelp = first_help; tHelp; tHelp = tHelp->next ) { if( pHelp->perm == tHelp->perm && strcmp( pHelp->keyword, tHelp->keyword ) == 0 ) { bug( "%s: duplicate: %s. Deleting.", __FUNCTION__, pHelp->keyword ); free_help( pHelp ); return; } else if( pHelp->keyword && ( ( match = strcmp( pHelp->keyword[0] == '\'' ? pHelp->keyword + 1 : pHelp->keyword, tHelp->keyword[0] == '\'' ? tHelp->keyword + 1 : tHelp->keyword ) ) < 0 || ( match == 0 && pHelp->perm > tHelp->perm ) ) ) { INSERT( pHelp, tHelp, first_help, next, prev ); break; } } if( !tHelp ) LINK( pHelp, first_help, last_help, next, prev ); top_help++; } void add_skill_help( SKILLTYPE *skill, bool update ) { HELP_DATA *pHelp; char buf[MSL]; if( !skill || !skill->name || !skill->htext ) return; snprintf( buf, sizeof( buf ), "'%s'", strupper( skill->name ) ); pHelp = get_help( NULL, buf ); if( pHelp && !update ) return; if( !pHelp ) { CREATE( pHelp, HELP_DATA, 1 ); STRSET( pHelp->keyword, buf ); STRSET( pHelp->text, skill->htext ); STRSET( pHelp->updated_by, "Skill" ); pHelp->updated = current_time; pHelp->nosave = true; pHelp->perm = PERM_ALL; add_help( pHelp ); } else { STRSET( pHelp->keyword, buf ); STRSET( pHelp->text, skill->htext ); STRSET( pHelp->updated_by, "Skill" ); pHelp->updated = current_time; pHelp->nosave = true; pHelp->perm = PERM_ALL; } } void add_command_help( CMDTYPE *cmd, bool update ) { HELP_DATA *pHelp; char buf[MSL]; if( !cmd || !cmd->name || !cmd->htext ) return; snprintf( buf, sizeof( buf ), "'%s'", strupper( cmd->name ) ); pHelp = get_help( NULL, buf ); if( pHelp && !update ) return; if( !pHelp ) /* New help file */ { CREATE( pHelp, HELP_DATA, 1 ); STRSET( pHelp->keyword, buf ); STRSET( pHelp->text, cmd->htext ); STRSET( pHelp->updated_by, "Command" ); pHelp->updated = current_time; pHelp->nosave = true; pHelp->perm = cmd->perm; add_help( pHelp ); } else /* Modify existing help file */ { STRSET( pHelp->keyword, buf ); STRSET( pHelp->text, cmd->htext ); pHelp->updated = current_time; pHelp->nosave = true; pHelp->perm = cmd->perm; } } HELP_DATA *new_help( void ) { HELP_DATA *phelp = NULL; CREATE( phelp, HELP_DATA, 1 ); if( !phelp ) { bug( "%s: phelp is NULL after CREATE.", __FUNCTION__ ); return NULL; } phelp->keyword = NULL; phelp->text = NULL; phelp->updated_by = NULL; phelp->nosave = false; return phelp; } void fread_help( FILE *fp ) { const char *word; bool fMatch; HELP_DATA *pHelp = NULL; pHelp = new_help( ); for( ;; ) { word = feof( fp ) ? "End" : fread_word( fp ); fMatch = false; switch( UPPER( word[0] ) ) { case '*': fMatch = true; fread_to_eol( fp ); break; case 'E': if( !str_cmp( word, "End" ) ) { if( !pHelp->keyword || pHelp->keyword[0] == '\0' ) { free_help( pHelp ); return; } if( pHelp->text && pHelp->text[0] == '.' && pHelp->text[1] == ' ' ) { char *tmptext = un_fix_help( pHelp->text ); STRSET( pHelp->text, tmptext ); } if( !str_cmp( pHelp->keyword, "greeting" ) ) help_greeting = pHelp->text; add_help( pHelp ); return; } break; case 'K': KEY( "Keyword", pHelp->keyword, fread_string( fp ) ); break; case 'P': KEY( "Perm", pHelp->perm, fread_number( fp ) ); break; case 'T': KEY( "Text", pHelp->text, fread_string( fp ) ); break; case 'U': KEY( "Updated", pHelp->updated, fread_time( fp ) ); KEY( "UpdatedBy", pHelp->updated_by, fread_string( fp ) ); break; } if( !fMatch ) { bug( "%s: no match: %s", __FUNCTION__, word ); fread_to_eol( fp ); } } if( pHelp ) free_help( pHelp ); } void load_helps( void ) { FILE *fp; top_help = 0; if( !( fp = fopen( HELP_FILE, "r" ) ) ) { bug( "%s: Can't open %s", __FUNCTION__, HELP_FILE ); perror( HELP_FILE ); return; } for( ;; ) { char letter; char *word; letter = fread_letter( fp ); if( letter == '*' ) { fread_to_eol( fp ); continue; } if( letter != '#' ) { bug( "%s: # not found.", __FUNCTION__ ); break; } word = fread_word( fp ); if( !str_cmp( word, "HELP" ) ) { fread_help( fp ); continue; } else if( !str_cmp( word, "END" ) ) break; else { bug( "%s: bad section (%s).", __FUNCTION__, word ); fread_to_eol( fp ); continue; } } fclose( fp ); fp = NULL; } HELP_DATA *get_help( CHAR_DATA *ch, char *argument ) { char argall[MIL], argone[MIL], argnew[MIL]; HELP_DATA *pHelp; int lev; if( !argument || argument[0] == '\0' ) argument = (char *)"summary"; if( isdigit( argument[0] ) && !is_number( argument ) ) { lev = number_argument( argument, argnew ); argument = argnew; } else lev = -2; /* Tricky argument handling so 'help a b' doesn't match a. */ argall[0] = '\0'; while( argument && argument[0] != '\0' ) { argument = one_argument( argument, argone ); if( argall[0] != '\0' ) mudstrlcat( argall, " ", sizeof( argall ) ); mudstrlcat( argall, argone, sizeof( argall ) ); } for( pHelp = first_help; pHelp; pHelp = pHelp->next ) { if( ch && pHelp->perm > get_trust( ch ) ) continue; if( lev != -2 && pHelp->perm != lev ) continue; if( is_name( argall, pHelp->keyword ) ) return pHelp; } return NULL; } /* LAWS command */ CMDF( do_laws ) { char buf[1024]; if( !argument || argument[0] == '\0' ) do_help( ch, (char *)"laws" ); else { snprintf( buf, sizeof( buf ), "law %s", argument ); do_help( ch, buf ); } } void show_help( CHAR_DATA *ch, HELP_DATA *pHelp ) { const char *h1, *h2; if( !ch || !pHelp ) return; h1 = color_str( AT_HELP, ch ); h2 = color_str( AT_HELP2, ch ); set_pager_color( AT_HELP, ch ); if( is_immortal( ch ) ) { pager_printf( ch, "%sHelp permission: %s%s%s\r\n", h1, h2, perms_flag[pHelp->perm], h1 ); pager_printf( ch, "%sHelp Keyword: %s%s&D%s\r\n", h1, h2, pHelp->keyword, h1 ); } if( pHelp->updated ) pager_printf( ch, "%sLast updated on %s%s&D %sby %s%s\r\n", h1, h2, distime( pHelp->updated ), h1, h2, pHelp->updated_by ? pHelp->updated_by : "(Unknown)" ); if( !pHelp->text ) return; pager_printf( ch, "%s.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.\r\n%s", h1, h2 ); send_to_pager( pHelp->text, ch ); pager_printf( ch, "%s.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.\r\n", h1 ); } void do_help( CHAR_DATA *ch, char *argument ) { HELP_DATA *pHelp, *lHelp = NULL; char *keyword, arg[MIL], oneword[MSL], lastmatch[MSL]; int value = -1; short matched = 0, checked = 0, totalmatched = 0, found = 0; bool uselevel = false; set_pager_color( AT_NOTE, ch ); if( !argument || argument[0] == '\0' ) argument = (char *)"summary"; if( ( pHelp = get_help( ch, argument ) ) ) { show_help( ch, pHelp ); return; } pager_printf( ch, "No help on \'%s\' found.\r\n", argument ); /* Get an arg incase they do a number seperate */ one_argument( argument, arg ); /* See if arg is a number if so update argument */ if( is_number( arg ) ) { argument = one_argument( argument, arg ); if( argument && argument[0] != '\0' ) { value = URANGE( 0, atoi( arg ), PERM_IMP ); uselevel = true; } else /* If no more argument put arg as argument */ argument = arg; } if( value >= 0 ) pager_printf( ch, "Checking for suggested helps that are permission %s.\r\n", perms_flag[value] ); send_to_pager( "Suggested Help Files:\r\n", ch ); strncpy( lastmatch, " ", sizeof( lastmatch ) ); /* Check helps first */ for( pHelp = first_help; pHelp; pHelp = pHelp->next ) { matched = 0; if( !pHelp || !pHelp->keyword || pHelp->keyword[0] == '\0' || pHelp->perm > get_trust( ch ) ) continue; /* Check arg if its avaliable */ if( uselevel && pHelp->perm != value ) continue; keyword = pHelp->keyword; while( keyword && keyword[0] != '\0' ) { matched = 0; /* Set to 0 for each time we check lol */ keyword = one_argument( keyword, oneword ); /* Lets check only up to 10 spots */ for( checked = 0; checked <= 10; checked++ ) { if( !oneword[checked] || !argument[checked] ) break; if( LOWER( oneword[checked] ) == LOWER( argument[checked] ) ) matched++; } if( ( matched > 1 && matched > ( checked / 2 ) ) || ( matched > 0 && checked < 2 ) ) { pager_printf( ch, " %-20s ", oneword ); if( ++found == 4 ) { found = 0; send_to_pager( "\r\n", ch ); } strncpy( lastmatch, oneword, MAX_STRING_LENGTH ); totalmatched++; lHelp = pHelp; break; } } } if( found != 0 ) send_to_pager( "\r\n", ch ); if( totalmatched == 0 ) send_to_pager( "No suggested help files.\r\n", ch ); else if( totalmatched == 1 && lHelp ) { send_to_pager( "Opening only suggested helpfile.\r\n", ch ); show_help( ch, lHelp ); } } extern char *help_greeting; /* so we can edit the greeting online */ CMDF( do_hedit ) { HELP_DATA *pHelp; if( !ch->desc ) { send_to_char( "You have no descriptor.\r\n", ch ); return; } switch( ch->substate ) { default: break; case SUB_HELP_EDIT: if( !( pHelp = ( HELP_DATA * ) ch->dest_buf ) ) { bug( "%s: sub_help_edit: NULL ch->dest_buf", __FUNCTION__ ); stop_editing( ch ); return; } if( help_greeting == pHelp->text ) help_greeting = NULL; STRFREE( pHelp->text ); pHelp->text = copy_buffer( ch ); if( !help_greeting ) help_greeting = pHelp->text; stop_editing( ch ); STRSET( pHelp->updated_by, ch->name ); pHelp->updated = current_time; return; } if( !( pHelp = get_help( ch, argument ) ) ) /* new help */ { HELP_DATA *tHelp; char argnew[MIL]; int lev; bool n_help = true; if( !argument || argument[0] == '\0' ) { send_to_char( "If you would like to create a new helpfile need to supply a keyword.\r\n", ch ); return; } for( tHelp = first_help; tHelp; tHelp = tHelp->next ) { if( !str_cmp( argument, tHelp->keyword ) ) { pHelp = tHelp; n_help = false; break; } } if( pHelp && pHelp->nosave ) { send_to_char( "This is a temporary help file that is associated with a skill or command.\r\n", ch ); send_to_char( "To edit this help file you will need to use cedit or sset.\r\n", ch ); return; } if( n_help ) { if( isdigit( argument[0] ) ) { lev = number_argument( argument, argnew ); argument = argnew; } else lev = get_trust( ch ); lev = URANGE( 0, lev, PERM_IMP ); pHelp = new_help( ); pHelp->keyword = STRALLOC( strupper( argument ) ); pHelp->perm = lev; add_help( pHelp ); } } if( pHelp && pHelp->nosave ) { send_to_char( "This is a temporary help file that is associated with a skill or command.\r\n", ch ); send_to_char( "To edit this help file you will need to use cedit or sset.\r\n", ch ); return; } ch->substate = SUB_HELP_EDIT; ch->dest_buf = pHelp; start_editing( ch, pHelp->text ); } /* Remove . if needed */ char *un_fix_help( char *text ) { static char newstr[MSL]; int i = 0, j = 0; if( !text || text[0] == '\0' ) return (char *)""; if( text[0] != '.' || text[1] != ' ' ) return text; if( text[0] == '.' ) i++; for( ; text[i] != '\0'; i++ ) newstr[j++] = text[i]; newstr[j] = '\0'; return newstr; } /* * Remove carriage returns from a line * add in a . if needed * preserve the normal spaces */ char *help_fix( char *text ) { static char newstr[MSL]; int i = 0, j = 0; if( !text || text[0] == '\0' ) return (char *)""; if( text[0] == ' ' ) newstr[j++] = '.'; for( ; text[i] != '\0'; i++ ) { if( text[i] != '\r' ) newstr[j++] = text[i]; } newstr[j] = '\0'; return newstr; } CMDF( do_hset ) { HELP_DATA *pHelp; char arg1[MIL], arg2[MIL]; argument = one_argument( argument, arg1 ); if( !arg1 || arg1[0] == '\0' ) { send_to_char( "Usage: hset <field> [value] [help page]\r\n\r\n", ch ); send_to_char( "Field being one of:\r\n", ch ); send_to_char( " perm keyword remove save\r\n", ch ); return; } if( !str_cmp( arg1, "save" ) ) { FILE *fp; char filename[MSL]; log_printf( "Saving %s", HELP_FILE ); snprintf( filename, sizeof( filename ), "%s.temp", HELP_FILE ); if( !( fp = fopen( filename, "w" ) ) ) { bug( "%s: cant open %s", __FUNCTION__, filename ); perror( filename ); return; } for( pHelp = first_help; pHelp; pHelp = pHelp->next ) { if( !pHelp->keyword || pHelp->nosave ) continue; fprintf( fp, "#HELP\n" ); if( pHelp->perm ) fprintf( fp, "Perm %d\n", pHelp->perm ); fprintf( fp, "Keyword %s~\n", pHelp->keyword ); if( pHelp->updated ) fprintf( fp, "Updated %ld\n", pHelp->updated ); if( pHelp->updated_by ) fprintf( fp, "UpdatedBy %s~\n", pHelp->updated_by ); if( pHelp->text ) fprintf( fp, "Text %s~\n", help_fix( pHelp->text ) ); fprintf( fp, "%s\n\n", "End" ); } fprintf( fp, "%s\n", "#END" ); fclose( fp ); fp = NULL; if( rename( filename, HELP_FILE ) ) bug( "%s: Couldn't rename (%s) to (%s).", __FUNCTION__, filename, HELP_FILE ); else chmod( HELP_FILE, S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH ); send_to_char( "Saved.\r\n", ch ); return; } if( str_cmp( arg1, "remove" ) ) argument = one_argument( argument, arg2 ); if( !argument || argument[0] == '\0' || !( pHelp = get_help( ch, argument ) ) ) { send_to_char( "Can't find help on that subject.\r\n", ch ); return; } if( !str_cmp( arg1, "remove" ) ) { UNLINK( pHelp, first_help, last_help, next, prev ); free_help( pHelp ); send_to_char( "Removed.\r\n", ch ); return; } if( !str_cmp( arg1, "perm" ) ) { int temp; if( !is_number( arg2 ) ) temp = get_flag( arg2, perms_flag, PERM_MAX ); else temp = atoi( arg2 ); if( temp < 0 || temp > get_trust( ch ) || temp >= PERM_MAX ) { send_to_char( "Invalid permission.\r\n", ch ); return; } pHelp->perm = temp; send_to_char( "Done.\r\n", ch ); return; } if( !str_cmp( arg1, "keyword" ) ) { if( !arg2 || arg2[0] == '\0' ) { send_to_char( "You can't set a keyword to nothing.\r\n", ch ); return; } STRSET( pHelp->keyword, strupper( arg2 ) ); send_to_char( "Done.\r\n", ch ); return; } do_hset( ch, (char *)"" ); } /* * Show help topics in a level range - Thoric * Idea suggested by Gorog * prefix keyword indexing added by Fireblade */ CMDF( do_hlist ) { HELP_DATA *help; char *keywords, *idx = NULL, arg[MIL], keyword[MIL]; const char *h1, *h2; int min, max, perm = -1, cnt = 0, level, col = 0, keycount = 0; bool hfound, kfound; min = 0; max = get_trust( ch ); argument = one_argument( argument, arg ); if( arg && arg[0] != '\0' ) { if( !isdigit( arg[0] ) && !is_number( arg ) ) { if( perm == -1 ) perm = get_flag( arg, perms_flag, PERM_MAX ); if( perm == -1 ) idx = arg; } else perm = URANGE( min, atoi( arg ), max ); } set_pager_color( AT_HELP, ch ); h1 = color_str( AT_HELP, ch ); h2 = color_str( AT_HELP2, ch ); if( perm == -1 ) pager_printf( ch, "%sHelp Topics in perm range %s%s %sto %s%s%s:\r\n\r\n", h1, h2, perms_flag[min], h1, h2, perms_flag[max], h1 ); else pager_printf( ch, "%sHelp Topics in perm %s%s\r\n\r\n", h1, h2, perms_flag[perm] ); for( level = min; level <= max; level++ ) { hfound = false; col = 0; for( help = first_help; help; help = help->next ) { kfound = false; if( help->perm == level && ( !idx || nifty_is_name_prefix( idx, help->keyword ) ) && ( perm == -1 || help->perm == perm ) ) { keywords = help->keyword; while( keywords && keywords[0] != '\0' ) { keywords = one_argument( keywords, keyword ); if( idx && !nifty_is_name_prefix( idx, keyword ) ) continue; if( !hfound ) pager_printf( ch, " %sPermission [%s%s%s]\r\n", h1, h2, perms_flag[level], h1 ); hfound = true; pager_printf( ch, "%s%20.20s&D", h2, keyword ); if( ++col == 3 ) { send_to_pager( "\r\n", ch ); col = 0; } else send_to_pager( " ", ch ); keycount++; kfound = true; } if( kfound ) ++cnt; } } if( col != 0 ) send_to_pager( "\r\n", ch ); } if( cnt ) pager_printf( ch, "\r\n%s%d %spage%s found with a total of %s%d %skeyword%s.\r\n", h2, cnt, h1, cnt != 1 ? "s" : "", h2, keycount, h1, keycount != 1 ? "s" : "" ); else pager_printf( ch, "%sNone found.\r\n", h1 ); } CMDF( do_credits ) { do_help( ch, (char *)"credits" ); } CMDF( do_check_helps ) { CMDTYPE *command; int col = 0, skcnt = 0, cmdcnt = 0, hash, sn; for( hash = 0; hash < 126; hash++ ) { for( command = command_hash[hash]; command; command = command->next ) { if( !command || !command->name || command->htext ) continue; if( ++cmdcnt == 1 ) send_to_pager( "\r\nCommands that have no help file:\r\n", ch ); pager_printf( ch, "%s%15.15s", col != 0 ? " " : "", command->name ); if( ++col == 5 ) { col = 0; send_to_pager( "\r\n", ch ); } } } if( col != 0 ) send_to_pager( "\r\n", ch ); col = 0; for( sn = 0; sn < top_sn; sn++ ) { if( !skill_table[sn] || !skill_table[sn]->name || skill_table[sn]->htext ) continue; if( ++skcnt == 1 ) send_to_pager( "\r\nSkills that have no help file:\r\n", ch ); pager_printf( ch, "%s%15.15s", col != 0 ? " " : "", skill_table[sn]->name ); if( ++col == 5 ) { col = 0; send_to_pager( "\r\n", ch ); } } if( col != 0 ) send_to_pager( "\r\n", ch ); if( !cmdcnt ) send_to_pager( "\r\nAll of your commands have matching help files.\r\n", ch ); else pager_printf( ch, "\r\n%d command%s were found to not have a matching help file.\r\n", cmdcnt, cmdcnt == 1 ? "" : "s" ); if( !skcnt ) send_to_pager( "All of your skills have matching help files.\r\n", ch ); else pager_printf( ch, "%d skill%s were found to not have a matching help file.\r\n", skcnt, skcnt == 1 ? "" : "s" ); } bool valid_help( char *argument ) { if( !argument || !get_help( NULL, argument ) ) return false; return true; } void update_skill_helps( void ) { HELP_DATA *phelp; int sn; for( sn = 0; sn < top_sn; sn++ ) { if( !skill_table[sn] || !skill_table[sn]->name || skill_table[sn]->htext ) continue; if( !( phelp = get_help( NULL, skill_table[sn]->name ) ) ) continue; STRSET( skill_table[sn]->htext, phelp->text ); phelp->nosave = true; bug( "%s: '%s' had no help text, found matching help file.", __FUNCTION__, skill_table[sn]->name ); } } void update_command_helps( void ) { HELP_DATA *phelp; CMDTYPE *command; int hash; for( hash = 0; hash < 126; hash++ ) { for( command = command_hash[hash]; command; command = command->next ) { if( !command || !command->name || command->htext ) continue; if( !( phelp = get_help( NULL, command->name ) ) ) continue; STRSET( command->htext, phelp->text ); phelp->nosave = true; bug( "%s: '%s' had no help text, found matching help file.", __FUNCTION__, command->name ); } } }