/* * Installation should be simple... Create the command entry in interp.c, * add a DECLARE_DO_FUN entry to interp.h, and then just copy the code * below into the bottom of your act_wiz.c file, should be a clean compile * but i make no promises and don't guarantee this code in any way... i'm * fairly certain there should be no drastic-type problems with it, but * this is the cover-my-ass statement that's so common in most snippets :D * I make no guarantee that this code won't screw your mud, and am in no * way responsible should this happen, you are installing this code at * your own risk and accept any consequences as your own problems... Now, * having said that, i can try to help troubleshoot any problems you may * have installing this, if you email me at velveeta_512#yahoo.com (just * replace the # with an @), or you can drop by my test port for Lace of * Ages at loa.slayn.net 9001... also, please report any bugs and/or you * find with this to me so that i can make the fixes on my end... There * might be a memory leak or 2 left in this thing, so if you find anything * just let me know :) Also, my codebase has been altered thus that some * of the structures referenced below are no longer in mine, so i wrote it * and debugged it until it worked on mine, and then altered it so that it * should work on a stock codebase, and haven't tested it after making * those additions... * * In conclusion, all I ask is that you retain the commented header line * below when you place it into your code, you don't even have to email * me and let me know if you're using it (though if you want to that's fine * with me :D)... and please send me those bug reports! */ /****************************************************************************************** * PList, written by Velveeta for Lace of Ages MUD: 05/07/2004 * * Used to get a listing of existent player files meeting certain * * criteria specified by the user, and sort it in a variety of ways. * * * * Sample usage: plist * * plist race human clan tinker class mage level > 30 laston < 7 sort level * ******************************************************************************************/ #define BYNAME 0 #define BYLEVEL 1 #define BYRACE 2 #define BYCLAN 3 #define BYCLASS 4 #define BYLASTON 5 typedef struct compare_data COMPARE_DATA; typedef struct pfile_data PFILE_DATA; typedef struct search_data SEARCH_DATA; struct compare_data { COMPARE_DATA * next; char * name; char operator; int value; bool valid; }; struct pfile_data { PFILE_DATA * next; char * name; sh_int level; sh_int race; sh_int clan; sh_int class; sh_int sex; long laston; bool valid; }; struct search_data { char * name; sh_int race; sh_int clan; sh_int class; sh_int sort; bool all; COMPARE_DATA * comparison; }; COMPARE_DATA *compare_free; COMPARE_DATA *new_compare( void ) { static COMPARE_DATA compare_zero; COMPARE_DATA *compare; if ( compare_free == NULL ) compare = alloc_perm( sizeof( *compare ) ); else { compare = compare_free; compare_free = compare_free->next; } *compare = compare_zero; VALIDATE(compare); return compare; } void free_compare( COMPARE_DATA *compare ) { if ( !IS_VALID(compare) ) return; if ( compare->name ) free_string(compare->name); INVALIDATE(compare); compare->next = compare_free; compare_free = compare; } PFILE_DATA *pfile_free; PFILE_DATA *new_pfile( void ) { static PFILE_DATA pfile_zero; PFILE_DATA *pfile; if ( pfile_free == NULL ) pfile = alloc_perm( sizeof( *pfile ) ); else { pfile = pfile_free; pfile_free = pfile_free->next; } *pfile = pfile_zero; VALIDATE(pfile); return pfile; } void free_pfile( PFILE_DATA *pfile ) { if ( !IS_VALID(pfile) ) return; if ( pfile->name ) free_string(pfile->name); INVALIDATE(pfile); pfile->next = pfile_free; pfile_free = pfile; } PFILE_DATA *pfile_matches; void display_plist_output( CHAR_DATA *ch, int count ) { BUFFER *output; char buf[MAX_STRING_LENGTH], race[MAX_STRING_LENGTH]; char class[MAX_STRING_LENGTH], laston[MAX_STRING_LENGTH]; int x; /* * This function displays the total output of the list of all found * matches, this is the only place you'll need to add color codes or * do any new formatting and alignment if you wish to add any new * fields... */ output = new_buf(); sprintf(buf, " Name Level Race Class Clan Laston\n\r"); add_buf(output,buf); sprintf(buf, "_______________________________________________________________________________\n\r"); add_buf(output,buf); sprintf(buf, "| | | | | | |\n\r"); add_buf(output,buf); for ( x = 0; x < count; x++ ) { sprintf(race, "%-5.5s", pfile_matches[x].race >= 0 ? capitalize(race_table[pfile_matches[x].race].name) : " "); sprintf(class, "%-5.5s", pfile_matches[x].class >= 0 ? capitalize(race_table[pfile_matches[x].class].name) : " "); strftime(laston, MAX_STRING_LENGTH - 1, "%a %b %e %H:%M:%S", ctime(current_time)); sprintf(buf, "| %-12s | %3d | %s | %s | %-14.14s | %-19.19s |\n\r", pfile_matches[x].name, pfile_matches[x].level, race, class, pfile_matches[x].clan > 0 ? capitalize(clan_table[pfile_matches[x].clan) : " ", laston ); add_buf(output,buf); } sprintf(buf, "| | | | | | | |\n\r"); add_buf(output,buf); sprintf(buf, "%d players found.\n\r", count); add_buf(output,buf); page_to_char(buf_string(output),ch); free_buf(output); } /* Privates to the sort_plist_matches function for qsort */ static int plist_name(const void *v1, const void *v2) { PFILE_DATA *i1 = (PFILE_DATA *) v1, *i2 = (PFILE_DATA *) v2; return strcmp(i1->name, i2->name); } static int plist_level(const void *v1, const void *v2) { PFILE_DATA *i1 = (PFILE_DATA *) v1, *i2 = (PFILE_DATA *) v2; return i1->level < i2->level ? -1 : (i1->level == i2->level ? 0 : 1 ); } static int plist_race(const void *v1, const void *v2) { PFILE_DATA *i1 = (PFILE_DATA *) v1, *i2 = (PFILE_DATA *) v2; return strcmp(race_table[i1->race].name, race_table[i2->race].name); } static int plist_class(const void *v1, const void *v2) { PFILE_DATA *i1 = (PFILE_DATA *) v1, *i2 = (PFILE_DATA *) v2; return strcmp(class_table[i1->class].name, class_table[i2->class].name); } static int plist_clan(const void *v1, const void *v2) { PFILE_DATA *i1 = (PFILE_DATA *) v1, *i2 = (PFILE_DATA *) v2; return strcmp(clan_table[UMAX(0,i1->clan)].name, clan_table[UMAX(0,i2->clan)].name); } static int plist_laston(const void *v1, const void *v2) { PFILE_DATA *i1 = (PFILE_DATA *) v1, *i2 = (PFILE_DATA *) v2; return i1->laston > i2->laston ? -1 : ( i1->laston == i2->laston ? 0 : 1 ); } void sort_plist_matches( int count, int sort ) { /* * Any new fields you wish to sort by should have a value defined * above, assigned below in get_pfile_matches, and then checked for * here, with a sorting function created above, you should be able * to figure out how to add any new sorting functions by looking at * all the other ones that are already created... */ switch( sort ) { case BYNAME: qsort( (void *) pfile_matches, count, sizeof(PFILE_DATA), plist_name ); break; case BYLEVEL: qsort( (void *) pfile_matches, count, sizeof(PFILE_DATA), plist_level ); break; case BYRACE: qsort( (void *) pfile_matches, count, sizeof(PFILE_DATA), plist_race ); break; case BYCLAN: qsort( (void *) pfile_matches, count, sizeof(PFILE_DATA), plist_clan ); break; case BYCLASS: qsort( (void *) pfile_matches, count, sizeof(PFILE_DATA), plist_class ); break; case BYLASTON: qsort( (void *) pfile_matches, count, sizeof(PFILE_DATA), plist_laston ); break; } } int get_plist_matches( SEARCH_DATA *criteria ) { int count = 0; PFILE_DATA *match; DESCRIPTOR_DATA *d; char *word, buf[MSL]; bool matchok, end = FALSE; DIR *player_dir; struct dirent *pfile; FILE *fp; /* * This function is sort of a slimmed-down version of fread_char, * intended to read in values to display in the output of the list * of matches... You may notice the new_pfile() function called * below, this was to eliminate a bug i was having with the mud * crashing randomly when checking match->name to see if it should * be freed before reassignment... it was given memory space with * a call to malloc, now it's given a recycling function just like * those you'd find in recycle.c... Any new fields you'd like * displayed should be read in here and assigned to a new field * that you add to the pfile data structure... i initialized all * numeric values to -2 since some lookup functions return -1 if * the value isn't found, and -2 can be used to check if a value * just wasn't read in period... */ match = new_pfile(); match->name = str_dup( "" ); match->level = -2; match->race = -2; match->clan = -2; match->class = -2; match->laston = -2; match->sex = -2; sprintf(buf, "%s", PLAYER_DIR); player_dir = opendir(buf); while( ( pfile = readdir(player_dir) ) != NULL ) { if ( pfile->d_name[0] == '.' ) continue; strcat(buf,pfile->d_name); if ( ( fp = file_open( buf,"r" ) ) != NULL ) { /* First, let's initialize match */ matchok = criteria->all; if ( !IS_NULLSTR(match->name) ) free_string( match->name ); match->name = str_dup( "" ); match->level = -2; match->race = -2; match->clan = -2; match->class = -2; match->laston = -2; match->sex = -2; for ( ; !end; ) { word = feof( fp ) ? "End" : fread_word( fp ); switch( UPPER(word[0]) ) { case '*': fread_to_eol( fp ); break; case 'C': if ( !str_cmp( word, "Clan" ) ) { match->clan = clan_lookup( fread_string( fp ) ); break; } if ( !str_cmp( word, "Cla" ) || !str_cmp( word, "Class" ) ) { match->clan = fread_number( fp ); break; } break; case 'E': if ( !str_cmp( word, "End" ) ) { file_close(fp); sprintf(buf,"%s",PLAYER_DIR); end = TRUE; break; } break; case 'L': if ( !str_cmp( word, "Lev" ) || !str_cmp( word, "Levl" ) || !str_cmp( word, "Level" ) ) { match->level = fread_number( fp ); break; } if ( !str_cmp( word, "LogO" ) ) { match->laston = fread_number( fp ); break; } break; case 'N': if ( !str_cmp( word, "Name" ) ) { match->name = fread_string( fp ); break; } break; case 'R': if ( !str_cmp( word, "Race" ) ) { match->race = race_lookup( fread_string( fp ) ); break; } break; case 'S': if ( !str_cmp( word, "Sex" ) ) { match->sex = fread_number( fp ); break; } break; } } end = FALSE; for ( d = descriptor_list; d != NULL; d = d->next ) { if ( CH(d) && !str_cmp( CH(d)->name, match->name ) ) /* Quick check to see if they're logged on */ { match->laston = current_time; break; } } if ( !matchok ) { COMPARE_DATA *comparison_new; if ( ( IS_NULLSTR(criteria->name) || !str_prefix(criteria->name, match->name) ) && ( criteria->race == -2 || criteria->race == match->race ) && ( criteria->class == -2 || criteria->class == match->class ) && ( criteria->clan == -2 || criteria->clan == match->clan ) ) matchok = TRUE; for ( comparison_new = criteria->comparison; comparison_new != NULL; comparison_new = comparison_new->next ) { if ( !str_prefix( comparison_new->name, "laston" ) ) { switch( comparison_new->operator ) { case '>': if ( current_time - match->laston <= ( comparison_new->value + 1 ) * 86400 ) matchok = FALSE; break; case '=': if ( current_time - match->laston < comparison_new->value * 86400 || current_time - match->laston >= ( comparison_new->value + 1 ) * 86400 ) matchok = FALSE; break; case '<': if ( current_time - match->laston >= comparison_new->value * 86400 ) matchok = FALSE; break; } } else if ( !str_prefix( comparison_new->name, "level" ) ) { switch( comparison_new->operator ) { case '>': if ( match->level <= comparison_new->value ) matchok = FALSE; break; case '=': if ( match->level < comparison_new->value || match->level > comparison_new->value ) matchok = FALSE; break; case '<': if ( match->level >= comparison_new->value ) matchok = FALSE; break; } } } } if ( matchok ) { if ( !count ) pfile_matches = malloc( sizeof( PFILE_DATA ) ); /* Use malloc so we can realloc as needed */ else { PFILE_DATA *new_table; new_table = realloc( pfile_matches, sizeof( PFILE_DATA ) * ( count + 1 ) ); if ( !new_table ) /* Reallocate failed */ { bug("Realloc failed in get_plist_matches.", 0); return 0; } pfile_matches = new_table; } pfile_matches[count].name = str_dup( match->name ); pfile_matches[count].level = match->level; pfile_matches[count].race = match->race; pfile_matches[count].clan = match->clan; pfile_matches[count].class = match->class; pfile_matches[count].laston = match->laston; pfile_matches[count].sex = match->sex; count++; } } } free_pfile(match); return count; } bool get_plist_criteria( CHAR_DATA *ch, char *argument, SEARCH_DATA *criteria ) { char arg[MAX_INPUT_LENGTH], buf[MAX_STRING_LENGTH]; bool correct_entry = TRUE, sorted = FALSE; /* Parse out all options on the command line */ criteria->all = TRUE; if ( !IS_NULLSTR(argument) ) /* If argument is NULL, show all matches */ { argument = one_argument( argument, arg ); /* * For regular arguments like clan, name, class, etc. we only * accept 1 entry, for things like level and laston, we can take * a range of values, like level < 30 level > 10... if you'd like * to be able to specify more than 1 name, clan, etc. you can use * the comparison linked list for them, and exclude the operator * field, just use name and value, and check later on with a * boolean variable to see if it finds a valid name prefix in * the list... just a thought :D */ for ( ; !IS_NULLSTR(arg); argument = one_argument(argument,arg) ) { if ( !str_prefix( arg, "clan" ) ) { argument = one_argument(argument, arg); criteria->all = FALSE; if ( criteria->clan != -2 ) { send_to_char("Duplicate entries for clan.\n\r", ch); correct_entry = FALSE; break;; } if ( ( criteria->clan = clan_lookup(arg) ) == -1 ) { sprintf(buf, "Invalid clan name: \"%s\"\n\r", arg); send_to_char(buf,ch); correct_entry = FALSE; break; } continue; } if ( !str_prefix( arg, "class" ) ) { argument = one_argument(argument, arg); criteria->all = FALSE; if ( criteria->class != -2 ) { send_to_char("Duplicate entries for class.\n\r", ch); correct_entry = FALSE; break;; } if ( ( criteria->class = class_lookup(arg) ) == -1 ) { sprintf(buf, "Invalid class name: \"%s\"\n\r", arg); send_to_char(buf,ch); correct_entry = FALSE; break; } continue; } else if ( !str_prefix( arg, "laston" ) || !str_prefix( arg, "level" ) ) { COMPARE_DATA *comparison_new; /* * We do allow multiple entries for laston and level, * just in case someone wants to see everyone that has * level > 30 and level < 50 or something... so we don't * bother checking for duplicate entries first... */ criteria->all = FALSE; comparison_new = new_compare(); comparison_new->name = str_dup( arg ); /* Store name of comparison */ argument = one_argument(argument, arg); /* Get next element, the operator */ if ( strlen(arg) > 1 ) /* Is operator more than 1 character? We only allow >, <, and = */ { sprintf(buf,"Invalid operator: \"%s\"\n\r", arg); send_to_char(buf,ch); correct_entry = FALSE; break; } comparison_new->operator = arg[0]; /* Store operator */ argument = one_argument(argument, arg); /* Get next element, the compare value */ if ( !is_number(arg) ) /* We only allow numeric values for comparisons */ { sprintf(buf,"Invalid comparison value: \"%s\"\n\r", arg); send_to_char(buf,ch); correct_entry = FALSE; break; } comparison_new->value = atoi(arg); comparison_new->next = criteria->comparison; /* Now tack it onto the list of checks */ criteria->comparison = comparison_new; continue; } else if ( !str_prefix( arg, "name" ) ) { argument = one_argument(argument, arg); criteria->all = FALSE; if ( !IS_NULLSTR(criteria->name) ) { send_to_char("Duplicate entries for name.\n\r", ch); correct_entry = FALSE; break; } criteria->name = str_dup(arg); continue; } else if ( !str_prefix( arg, "race" ) ) { argument = one_argument(argument, arg); criteria->all = FALSE; if ( criteria->race != -2 ) { send_to_char("Duplicate entries for race.\n\r", ch); correct_entry = FALSE; break; } if ( ( criteria->race = race_lookup(arg) ) == -1 ) { sprintf(buf, "Invalid race name: \"%s\"\n\r", arg); send_to_char(buf,ch); correct_entry = FALSE; break; } continue; } else if ( !str_prefix( arg, "sort" ) ) { argument = one_argument(argument, arg); /* * Any additional fields you want to add for sorting, simply * define a new value for them, add the string check below, * add the new value to the switch statement in the sorting * function above, and then create a new private function for * qsort to sort the list with... */ if ( sorted ) { send_to_char("Duplicate sorting options.\n\r", ch); correct_entry = FALSE; break; } sorted = TRUE; if ( !str_prefix( arg, "clan" ) ) criteria->sort = BYCLAN; else if ( !str_prefix( arg, "class" ) ) criteria->sort = BYCLASS; else if ( !str_prefix( arg, "laston" ) ) criteria->sort = BYLASTON; else if ( !str_prefix( arg, "level" ) ) criteria->sort = BYLEVEL; else if ( !str_prefix( arg, "name" ) ) criteria->sort = BYNAME; else if ( !str_prefix( arg, "race" ) ) criteria->sort = BYRACE; continue; } else { sprintf(buf, "Invalid search type: \"%s\"\n\r", arg); send_to_char(buf,ch); correct_entry = FALSE; break; } } if ( !correct_entry ) /* Destroy and free any memory that was assigned */ { COMPARE_DATA *comparison_new; /* * Any additional fields you may add to the compare data structure, * make sure you free any necessary variables below in order to avoid * memory leaks... */ if ( !IS_NULLSTR(criteria->name) ) free_string(criteria->name); if ( criteria->comparison ) { for ( ; criteria->comparison != NULL; criteria->comparison = comparison_new ) { comparison_new = criteria->comparison->next; free_compare(criteria->comparison); } } return FALSE; } } return TRUE; } void do_plist( CHAR_DATA *ch, char *argument ) { SEARCH_DATA *criteria; int count = 0, x; /* First things first, let's initialize criteria */ criteria = malloc( sizeof( SEARCH_DATA ) ); criteria->name = str_dup( "" ); criteria->race = -2; criteria->clan = -2; criteria->sort = BYNAME; criteria->all = FALSE; criteria->comparison = NULL; /* Now to parse out the command line into the actual criteria... */ if ( !get_plist_criteria( ch, argument, criteria ) ) /* If FALSE, something went wrong */ return; /* * Ok, if we've reached this point, it means that we've entered * a line of queries with the appropriate syntax and we're ready * to find all the matching pfiles now... so here we go... */ if ( ( count = get_plist_matches( criteria ) ) > 0 ) { /* * Found at least 1 matching pfile, now sort them and display * them to the user... */ sort_plist_matches( count, criteria->sort ); display_plist_output( ch, count ); } else { send_to_char("No matching players were found.\n\r", ch); return; } /* * Now free all the name strings that have been assigned as the * plist was built, and then free the whole chunk of memory for * the array itself along w/ the chunk for criteria... */ for ( x = 0; x < count - 1; x++ ) if ( !IS_NULLSTR(pfile_matches[x].name) ) free_string(pfile_matches[x].name); free(criteria); free(pfile_matches); }