/**************************************************************************/
// intro.cpp - Introduction system - Kal May 2000
/***************************************************************************
* The Dawn of Time v1.69r (c)1997-2004 Michael Garratt *
* >> A number of people have contributed to the Dawn codebase, with the *
* majority of code written by Michael Garratt - www.dawnoftime.org *
* >> To use this source code, you must fully comply with the dawn license *
* in licenses.txt... In particular, you may not remove this copyright *
* notice. *
**************************************************************************/
/**************************************************************************/
// Unfinished like most of dawn *grin*, currently uses 2 bit flags to cache
// who knows who... if you have more than 512 people logged in at once make
// sure to increase MAX_CACHED_LOOKUPS ;)
// A future project could be to allow custom names... a bit cache value of
// 01 could be used to mark these... currently:
// 00 = uncached, 01 = unused, 10 = unknown name, 11 = known name.
/**************************************************************************/
#include "include.h"
#include "intro.h"
#define MAX_KNOW_SET 10000
intro_data *intro_data_database[MAX_KNOW_SET];
#define MAX_CACHED_LOOKUPS (512)// after 512 logins, we reinitialise
// the complete intro_cache
#define BYTES_TO_STORE_CACHE (MAX_CACHED_LOOKUPS%4==0? MAX_CACHED_LOOKUPS/4:(MAX_CACHED_LOOKUPS/4)+1)
/**************************************************************************/
// intro debugging log
void ilogf(char *fmt, ...)
{
if(!GAMESETTING5(GAMESET5_VERBOSE_INTRODUCTION_LOGGING)){
return; // introduction logging disabled by default
}
char buf[MSL*2];
va_list args;
va_start(args, fmt);
vsnprintf(buf, MSL*2, fmt, args);
va_end(args);
append_datetimestring_to_file( INTRO_DEBUG_FILE, buf);
log_string(buf);
}
/**************************************************************************/
unsigned short find_empty_know_node()
{
for(unsigned short i=10; i<MAX_KNOW_SET; i++){
if(!intro_data_database[i]){
return i;
}
}
bugf("unsigned short find_empty_know_node(), couldn't find an empty node!\n"
"System needs improvement for dynamic increasement here!");
do_abort();
return 0;
}
/**************************************************************************/
// returns the know index of a player, based on its player id
unsigned short find_know_index_from_char_id(time_t id)
{
for(unsigned short i=10; i<MAX_KNOW_SET; i++){
if(intro_data_database[i]){
if(intro_data_database[i]->owner_id==id){
return i;
}
}
}
return 0;
}
/**************************************************************************/
void intro_data::allocate_intro_cache()
{
static int last_allocated_cache_index=0;
last_allocated_cache_index++;
if(last_allocated_cache_index>MAX_CACHED_LOOKUPS-1){
// we need to reinitialise the complete cache
ilogf("intro_data::allocate_cache_details() - No space, reinitialising the complete cache");
logf("intro_data::allocate_cache_details() - No space, reinitialising the complete cache");
// restart the index
last_allocated_cache_index=0;
// loop thru deallocating the complete cache
for(unsigned short i=10; i<MAX_KNOW_SET; i++){
if(intro_data_database[i]){
if(intro_data_database[i]->intro_cache){
free(intro_data_database[i]->intro_cache);
intro_data_database[i]->intro_cache=NULL;
}
intro_cache_index=0;
}
}
// now loop thru all the players, reallocating them new cache
for( char_data *ch = player_list; ch; ch = ch->next_player ) {
ch->know->allocate_intro_cache();
}
}else{
intro_cache=(unsigned char *)malloc(BYTES_TO_STORE_CACHE);
intro_cache_index=last_allocated_cache_index;
ilogf("Allocated intro_cache_index %d to know_index %d",
intro_cache_index, my_know_index);
}
}
/**************************************************************************/
void intro_data::assign_owner(char_data *ch)
{
ilogf("intro_data::assign_owner() assigning know index %d to %s(%d)",
my_know_index, ch->name, ch->know_index);
assert(ch->id==owner_id);
assert(ch->know_index==my_know_index);
owner=ch;
last_logged_in=current_time;
ch->know=this;
if(!intro_cache){ // allocate memory for the cached lookups
allocate_intro_cache();
}
logf("intro_data::assign_owner() assigned know index %d to %s(%d)[%d]",
my_know_index, ch->name, ch->know_index, intro_cache_index);
ilogf("intro_data::assign_owner() assigned know index %d to %s(%d)[%d]",
my_know_index, ch->name, ch->know_index, intro_cache_index);
}
/**************************************************************************/
// link who a player knows to their pointer
void attach_know(char_data* player)
{
if(IS_NPC(player)){
player->know=NULL;
return;
}
if(intro_data_database[player->know_index]==NULL){
player->know_index=0;
}
// 'player->know_id' is treated as what their id might possibly
// this way if it doesn't match due to database corruption, we can reassign
if(player->know_index==0){ // new char
player->know_index=find_know_index_from_char_id(player->id);
if(player->know_index==0){
player->know_index=find_empty_know_node();
assert(player->know_index<MAX_KNOW_SET);
assert(intro_data_database[player->know_index]==NULL);
intro_data_database[player->know_index]=new intro_data;
intro_data_database[player->know_index]->owner_id=player->id;
intro_data_database[player->know_index]->my_know_index=player->know_index;
}
}
assert(player->know_index<MAX_KNOW_SET);
if(intro_data_database[player->know_index]->owner_id!=player->id){
ilogf("intro_data_database[player->know_index]->owner_id!=player->id, player=%s, knowindex=%d",
player->name, player->know_index);
bugf("intro_data_database[player->know_index]->owner_id!=player->id, player=%s, knowindex=%d",
player->name, player->know_index);
// time to get allocated a new index - rerun the function
player->know_index=0;
attach_know(player);
return;
}
assert(player->id==intro_data_database[player->know_index]->owner_id);
assert(player->know_index==intro_data_database[player->know_index]->my_know_index);
// set player->know to point the correct location etc
intro_data_database[player->know_index]->assign_owner(player);
};
/**************************************************************************/
intro_node * intro_node::find_know_node_by_index(unsigned short lookup_know_index)
{
if(!this){
return NULL;
}
if(know_index==lookup_know_index){
return this;
}
intro_node *node=next;
for(; node; node=node->next){
if(node->know_index==lookup_know_index){
return node;
}
};
return NULL;
};
/**************************************************************************/
// return false if they arent new
bool intro_node::add_person_by_index(unsigned short new_person_know_index, intro_data *know)
{
if(!know){
ilogf("intro_node::add_person_by_index(unsigned short new_person_know_index, intro_data *know), know==NULL!");
make_corefile();
return false;
}
if(know->people && know->people->find_know_node_by_index(new_person_know_index)){
return false; // already known
}
// add a new person
know->load_person(new_person_know_index, current_time);
return true;
}
/**************************************************************************/
// load/add a relationship about new_person_id knowing 'this'
bool intro_data::load_person(unsigned short new_person_id, time_t last_seen)
{
// add a new person
intro_node *node= new intro_node;
node->know_index=new_person_id;
node->last_seen=last_seen;
node->next=people;
people=node;
return true;
}
/**************************************************************************/
// Return true if ch knows the target
bool intro_data::knows(char_data *person)
{
if(IS_NPC(person) || !person->know){
return false;
}
if(!people){
return false;
}
int cache_index=intro_data_database[person->know->my_know_index]->intro_cache_index;
assert(cache_index); // have to have a cache index.. if they dont, how did they get here?
// check if we have it cached
if(IS_SET(intro_cache[cache_index/4], 1<<((cache_index%4)*2))){
// bit caching is fast :)
if(IS_SET(intro_cache[cache_index/4], 1<<(((cache_index%4)*2)+1) ) ){
ilogf("Intro cache hit true for %d looking at %d (%d)", my_know_index, person->know->my_know_index, cache_index);
return true;
}
ilogf("Intro cache hit false for %d looking at %d (%d)", my_know_index, person->know->my_know_index, cache_index);
return false;
}
// support cached lookups later?
intro_node *node= people->find_know_node_by_index(person->know->my_know_index);
// cache the result
SET_BIT(intro_cache[cache_index/4], 1<<((cache_index%4)*2)); // we are caching
ilogf("Recording cache update for %d looking at %d (%d)", my_know_index, person->know->my_know_index, cache_index);
if(node){
node->last_seen=current_time; // record so we dont forget ever seeing them
ilogf("Intro cache miss for %d looking at %d (%d)", my_know_index, person->know->my_know_index, cache_index);
SET_BIT(intro_cache[cache_index/4], 1<<(((cache_index%4)*2)+1) ); // they do know them (cache it)
return true;
}
REMOVE_BIT(intro_cache[cache_index/4], 1<<(((cache_index%4)*2)+1) ); // they do NOT know them (cache it)
return false;
};
/**************************************************************************/
// when you are introduced to someone
bool intro_data::introduced_to(char_data *person)
{
if(!this){
ilogf("intro_data::introduced_to('%s'), this==NULL!", person?person->name:"null person also");
return false;
}
if(owner->know!=this){
ilogf("intro_data::introduced_to('%s'), this==NULL!", person?person->name:"null person also");
return false;
}
assert(owner->know==this);
// update the caching system
int cache_index=intro_data_database[person->know->my_know_index]->intro_cache_index;
SET_BIT(intro_cache[cache_index/4], 1<<(((cache_index%4)*2)) ); // they now know them
SET_BIT(intro_cache[cache_index/4], 1<<(((cache_index%4)*2)+1) ); // they now know them
ilogf("intro_data::introduced_to(): (introduction) recording cache update for %d knowing %d (%d)",
my_know_index, person->know->my_know_index, cache_index);
return(people->add_person_by_index(person->know->my_know_index, this));
};
/**************************************************************************/
// when you choose to forget someone
void intro_data::forgetting(char_data *person)
{
if(!this){
ilogf("intro_data::forgetting('%s'), this==NULL!", person?person->name:"null person also");
return;
}
if(owner->know!=this){
ilogf("intro_data::forgetting('%s'), this==NULL!", person?person->name:"null person also");
return;
}
assert(owner->know==this);
if(!people){
return; // if we dont know anyone
}
// update the caching system
int cache_index=intro_data_database[person->know->my_know_index]->intro_cache_index;
REMOVE_BIT(intro_cache[cache_index/4], 1<<(((cache_index%4)*2)) ); // they now know them
REMOVE_BIT(intro_cache[cache_index/4], 1<<(((cache_index%4)*2)+1) ); // they now know them
ilogf("intro_data::forgetting(): (forget) cache cleared for %d not knowing %d (%d)",
my_know_index, person->know->my_know_index, cache_index);
{ // remove them
ilogf("Removing");
int mki=person->know->my_know_index;
intro_node *prev=people;
if(people->know_index==mki){
people=people->next;
delete prev;
ilogf("delete prev; - removed");
return;
}else{
for(intro_node *delnode=people->next; delnode; delnode=delnode->next){
if(delnode->know_index==mki){
prev->next=delnode->next;
delete delnode;
ilogf("delete delnode; - removed");
return;
}
prev=delnode;
}
}
ilogf("not removed?!?");
}
};
/**************************************************************************/
// Save the database of 'who knows who' to disk.
// saving is done in 2 stages:
// * the first stage saves the data linking a player id to their 'know index'
// * stage two saves which know indexs know others.
// This means we can discard old know data for deleted players
// when reading in the database.
void save_intro_database()
{
unsigned short i;
intro_node *node;
char filename[MIL];
sprintf(filename, "%s.write", INTRODB_FILE);
logf("Saving introduction database to %s", filename);
FILE *fp=fopen( filename,"w");
if(!fp){
bugf("save_intro_database(): Couldn't open file '%s' to save introduction database to!",
filename);
return;
}
// stage 1
for(i=10; i<MAX_KNOW_SET; i++){
if(intro_data_database[i] && intro_data_database[i]->save_in_db){
// owner player ID is compulsory in the database, since this is
// what everything is linked back against in attach_know
// if an index to player id mapping not saved, the
// data relating to that node will be dropped reboot
if(intro_data_database[i]->last_logged_in< current_time- (60*60*24*90)){
// if you haven't logged on for 90 days you are dropped
// from the database
continue;
}
fprintf(fp,"-4 %d %d %d\n",
i,
(int)intro_data_database[i]->owner_id,
(int)intro_data_database[i]->last_logged_in);
}
}
// stage 2 - save which indexes know which indexes, and last seen
for(i=10; i<MAX_KNOW_SET; i++){
if(intro_data_database[i] && intro_data_database[i]->save_in_db){
for(node=intro_data_database[i]->people; node; node=node->next){
if(intro_data_database[node->know_index]){
fprintf(fp,"%d %d %d\n", i, node->know_index, (int)node->last_seen);
}
}
}
}
int bytes_written=fprintf(fp,"-1\n");
fclose( fp );
if( bytes_written != str_len("-1\n") ){
bugf("save_intro_database(): fprintf to '%s' incomplete - error %d (%s)",
filename, errno, strerror( errno));
bugf("Incomplete write of %s, write aborted - check diskspace!", filename);
autonote(NOTE_SNOTE, "save_intro_database()",
"Problems saving class table", "code cc: imm",
"Incomplete write of " INTRODB_FILE ".write, write aborted - check diskspace!\r\n", true);
}else{
logf("Renaming old " INTRODB_FILE " to " INTRODB_FILE ".bak");
unlink(INTRODB_FILE".bak");
rename(INTRODB_FILE, INTRODB_FILE".bak");
logf("Renaming new %s to " INTRODB_FILE, filename);
unlink(INTRODB_FILE);
rename(filename, INTRODB_FILE);
}
logf("Finished saving introduction database to %s", INTRODB_FILE);
}
/**************************************************************************/
// called when a character is deleting... we flag their know info so it
// isn't saved to disk
void intro_player_delete(char_data *player)
{
if(!player){
return;
}
// bounds check
assert(player->know_index>0);
assert(player->know_index<MAX_KNOW_SET);
if(intro_data_database[player->know_index]->owner_id!=player->id){
ilogf("intro_player_delete(): intro_data_database[player->know_index]->owner_id!=player->id, player=%s, knowindex=%d",
player->name, player->know_index);
bugf("intro_player_delete(): intro_data_database[player->know_index]->owner_id!=player->id, player=%s, knowindex=%d",
player->name, player->know_index);
// looks like we have some corruption
do_abort();
return;
}
// do a few double checks
assert(player->id==intro_data_database[player->know_index]->owner_id);
assert(player->know_index==intro_data_database[player->know_index]->my_know_index);
// set player->know to point the correct location etc
intro_data_database[player->know_index]->save_in_db=false;
ilogf("intro_player_delete(): player %s(%d)[%d] flagged for intro database removal.",
player->name, player->id, player->know_index);
logf("intro_player_delete(): player %s(%d)[%d] flagged for intro database removal.",
player->name, (int)player->id, player->know_index);
}
/**************************************************************************/
void load_intro_database()
{
logf("Load introduction database from %s", INTRODB_FILE);
FILE *fp=fopen( INTRODB_FILE,"r");
memset(&intro_data_database[0], 0, sizeof(intro_data*)* (MAX_KNOW_SET));
if(!fp){
bugf("load_intro_database(): Couldn't open file '%s' to load introduction database from!",
INTRODB_FILE);
log_note("NOTE: The introduction database stores who knows who, if this is a new mud "
"then it is expected that this file is missing until a mortal uses the "
"introduction system. If this is an existing mud which is making use of the "
"introduction system, then player introductions will have to be recreated. "
"It is safe to delete this file at any stage if required.");
return;
}
int know_index=0;
while(!feof(fp)){
know_index=fread_number(fp);
if(know_index==-1){// end of file marker
break;
}
if(know_index==-2){// comment to end of line
fread_to_eol(fp); // allow expansion later on/comments
continue;
}
if(know_index==-3 || know_index==-4){// know index to owner id cross reference data (save stage 1)
int nlast_logged_in;
bool read_last_login=true;
if(know_index==-3){
read_last_login=false;
}
know_index=fread_number(fp);
if(know_index<10 || know_index>MAX_KNOW_SET){
bugf("load_intro_database(): -3 know_index %d invalid!"
"- not within level valid range... exiting!", know_index);
exit_error( 1 , "load_intro_database", "-3 know_index invalid");
}
int nid=fread_number(fp);
if(!intro_data_database[know_index]){
intro_data_database[know_index]=new intro_data;
}
if(!intro_data_database[know_index]){
intro_data_database[know_index]=new intro_data;
}
intro_data_database[know_index]->owner_id=nid;
intro_data_database[know_index]->my_know_index=know_index;
if(read_last_login){
nlast_logged_in=fread_number(fp);
}else{ // set a default value
nlast_logged_in=(int)current_time;
}
intro_data_database[know_index]->last_logged_in=nlast_logged_in;
fread_to_eol(fp); // allow expansion later on/comments
continue;
}
// below here is assumed to be a result of stage 2 saving
if(know_index<10 || know_index>MAX_KNOW_SET){
bugf("load_intro_database(): know_index %d invalid!"
"- not within level valid range... exiting!", know_index);
exit_error( 1 , "load_intro_database", "know_index invalid");
}
int nindex=fread_number(fp);
int nlastseen=fread_number(fp);
fread_to_eol(fp); // allow expansion later on/comments
// only if both people involved still exist, will it
// accept the know info
if(intro_data_database[know_index] && intro_data_database[nindex]){
intro_data_database[know_index]->load_person(nindex, nlastseen);
}
}
if(feof(fp) && know_index!=-1){
logf("load_intro_database(): load incomplete, exiting!");
exit_error( 1 , "load_intro_database", "load incomplete");
}
logf("load_intro_database(): Load complete.");
fclose(fp);
}
/**************************************************************************/
/**************************************************************************/