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