/*
* $Locker: $
* $Id: playerinfo.c,v 1.63 2003/05/31 18:40:26 ceres Exp $
*/
/**
* Playerinfo database handler.
*
* This handler collects all the fascinating information about players and
* their sins. At the moment, the following events are supported:
* replace (all replacements, added by Presto's wand),
* gag (gagging and ungagging, added by the gagger),
* suspend (player suspension, added by the "suspend" command),
* meteor (meteoring a player, added by the "meteor" command),
* multiplay (various events added by the multiplayer handler),
* harassment (comments about cases of harassment, added via 'addevent'),
* misc (misc. comments, added via 'addevent'),
* cheat (currently unused)
*
* The "replace" and "multiplay" events are debounced (merged). In addition,
* the "replace" events expire in 30 days.
*
* @author Fiona
*/
#include <playerinfo.h>
#include <player_handler.h>
#include <refresh.h>
#include <applications.h>
#include <newbiehelpers.h>
// This is where our save files go
#define SAVE_DIR "/save/playerinfo"
#define SAVE_FILE ( SAVE_DIR + "/handler_data" )
#define NO_ACCESS_LOG 6
// Originators of function calls. Checked for security reasons
#define LIAISON_WAND "/d/liaison/items/wand"
#define LIAISON_SCEPTRE "/d/liaison/items/mort_sceptre"
#define LIAISON_PEN "/d/liaison/items/quota_pen"
#define LIAISON_INTERVIEW "/d/liaison/utils/interview"
#define MULTIPLAY "/obj/handlers/multiplayer"
#define GAG "/cmds/creator/gag"
#define UNGAG "/cmds/creator/ungag"
#define METEOR "/cmds/creator/meteor"
#define FRY "/cmds/creator/fry"
#define REPORT_COMMAND "/cmds/creator/playerinfo"
#define ADD_COMMAND "/cmds/creator/addevent"
#define DELETE_COMMAND "/cmds/creator/delevent"
#define ADDALT_COMMAND "/cmds/creator/addalt"
#define DELALT_COMMAND "/cmds/creator/delalt"
#define FAMILY_COMMAND "/cmds/creator/family"
#define SUSPENDER "/secure/bastards"
#define SHOWHELP_COMMAND "/cmds/creator/show_help"
#define PATRICIAN_PALACE "/d/am/patrician/patrician"
#define REFRESH_HANDLER "/obj/handlers/refresh"
#define PRISON "/d/sur/beta/prison/dungeon"
#define FETCH_COMMAND "/cmds/creator/fetch"
#define REARRANGE_COMMAND "/cmds/guild-race/rearrange"
#define PATRICIAN_PT "/d/playtesters/handlers/applications"
#define PATRICIAN_COMPLAIN "/d/am/patrician/pat_complaints"
#define RUN_HANDLER "/obj/handlers/guild_things/run"
#define BANISH_COMMAND "/secure/cmds/creator/banish"
#define FAIRY_GODMOTHER "/obj/handlers/fairy_godmothers"
#define CACHE_SIZE 150
// These don't really belong here... and we have /include/colour.h...
// ...oh well.
#define CL_CREATOR "%^CYAN%^"
#define CL_EVENT "%^RED%^"
#define CL_RESET "%^RESET%^"
#define CL_HEADER "%^RED%^"
// This is the interval at which debounced events are saved.
#define DEBOUNCE_PERIOD (60*30)
// This is the interval between consecutive checks for timeouts.
// One day is suffucuent.
#define TIMEOUT_PERIOD (60*60*24)
//This is one week.
#define ONE_WEEK ( 60 * 60 * 24 * 7 )
nosave inherit "/std/object";
// Function prototypes
mapping query_timeouts();
protected string filename(string name);
string * query_events();
protected int query_debounced(string event);
protected int query_lord_only(string event);
int query_source_ok(string event, string source);
protected int query_deleter_ok(string event, object deleter);
protected int query_can_add(string e, object p);
protected int query_can_delete(string e, object p);
protected int query_can_handle_alts(object p);
protected void do_debouncing(string player, class dbentry entry);
protected void do_timeout();
protected void load_player(string player);
protected void save_player(string player);
void player_remove(string player);
int add_entry(object creator, string player, string event,
string comment, mixed *extra);
int delete_entry(object creator, string player, string event,
int n);
protected string query_header(string player);
protected string query_entry(int idx, class dbentry e,
string display_name);
string query_access_log(object source, string player, int lastn);
string query_event(object source, string player, string event);
string add_alt(object creator, string player, string alt);
string delete_alt(object creator, string player, string alt);
mapping query_alerts();
int query_alerts_for( string player );
int is_alert( string player, int idx );
int acknowledge_alert( object creator, string player,
string event, int idx, string update, mixed * extra );
void clear_alerts_for( string player );
int increment_alerts_for( string player );
int decrement_alerts_for( string player );
void correct_alts_for( string player );
void player_deleted(mixed player, int deleted);
void fix_alts(mixed player);
class source {
string *add;
string *delete;
}
// This is where the current player's info resides.
private class playerinfo dossier;
// Event timeout information (in seconds)
private nosave mapping timeouts = ([ "replace" : (60*60*24*30) ]);
// Ok guys, NEVER do things like this, it is highly poisonous...
private int local_time;
private nosave mapping _sources;
nosave mapping _alerts; // format: ([ "playername1":n, "playername2":n ... ])
// where n is the number of alerts in their dossier.
nosave string * _lordonly;
private nosave int correcting_alts; // used to control the alt correction
// process so we don't loop infinitely.
private nosave int no_recurse;
private nosave mapping _dossier_cache;
private nosave int _dossier_cache_hits;
private nosave int _dossier_total;
void save_handler_data() {
mapping vars = ([ ]);
string tmp;
vars["alerts"] = _alerts;
tmp = save_variable( vars );
unguarded( (: write_file, SAVE_FILE, tmp, 1 :) );
} /* save_handler_data() */
void load_handler_data() {
mapping vars;
string tmp;
if( file_size( SAVE_FILE ) <= 0 )
return;
tmp = unguarded( (: read_file, SAVE_FILE :) );
vars = restore_variable( tmp );
_alerts = vars["alerts"];
} /* load_handler_data() */
void create() {
::create();
_sources = ([
"cheat": new(class source,
add : ({ }),
delete : ({ DELETE_COMMAND })),
"discipline": new(class source,
add : ({ ADD_COMMAND, PRISON, REPORT_COMMAND,
BANISH_COMMAND }),
delete : ({ DELETE_COMMAND })),
"email": new(class source,
add : ({ }),
delete : ({ DELETE_COMMAND })),
"family": new(class source,
add : ({ FAMILY_COMMAND }),
delete : ({ DELETE_COMMAND })),
"gag": new(class source,
add : ({ GAG, UNGAG,
ADD_COMMAND, REPORT_COMMAND }),
delete : ({ DELETE_COMMAND })),
"harassment": new(class source,
add : ({ ADD_COMMAND, REPORT_COMMAND }),
delete : ({ DELETE_COMMAND })),
"meteor": new(class source,
add : ({ METEOR, FRY }),
delete : ({ DELETE_COMMAND })),
"misc": new(class source,
add : ({ ADD_COMMAND, APPLICATIONS_HANDLER,
REARRANGE_COMMAND, PATRICIAN_PT,
PATRICIAN_COMPLAIN, RUN_HANDLER,
REPORT_COMMAND, NEWBIEHELPERS_HANDLER,
LIAISON_SCEPTRE,
LIAISON_PEN, FAIRY_GODMOTHER,
LIAISON_INTERVIEW }),
delete : ({ DELETE_COMMAND })),
"multiplay": new(class source,
add : ({ MULTIPLAY, ADD_COMMAND,
REPORT_COMMAND }),
delete : ({ DELETE_COMMAND })),
"replace": new(class source,
add : ({ LIAISON_WAND,
ADD_COMMAND, FETCH_COMMAND,
REPORT_COMMAND }),
delete : ({ DELETE_COMMAND })),
"showhelp": new(class source,
add : ({ SHOWHELP_COMMAND }),
delete : ({ DELETE_COMMAND })),
"suspend": new(class source,
add : ({ SUSPENDER }),
delete : ({ })),
"alert": new(class source,
add : ({ ADD_COMMAND, REPORT_COMMAND }),
delete : ({ DELETE_COMMAND })),
"refresh": new(class source,
add : ({ REFRESH_HANDLER }),
delete : ({ DELETE_COMMAND })),
"delete": new(class source,
add : ({ REFRESH_HANDLER }),
delete : ({ DELETE_COMMAND }))
]);
_alerts = ([ ]);
_lordonly = ({ });
_dossier_cache = ([ ]);
_dossier_cache_hits = 0;
_dossier_total = 0;
seteuid("Room");
load_handler_data();
} /* create() */
// Return the mapping of event timeouts
mapping query_timeouts() {
return timeouts;
} /* query_timeouts() */
string query_name() {
return "playerinfo handler";
}
/**
* Give the filename for the player's savefile.
* @param name the name of the player
* @return the name of the playerinfo file
* @ignore
*/
protected string filename(string name) {
string p = lower_case(name);
return sprintf("%s/%c/%s.o",SAVE_DIR,p[0],p);
} /* filename() */
/**
* Answer the list of all possible events.
* @return array of all event types recognized by the playerinfo handler
*/
string *query_events() {
return keys(_sources);
} /* query_events() */
/**
* Check if the event should be debounced.
* @param event the name of the event
* @return nonzero if the given event is to be debounced
* @ignore
*/
protected int query_debounced(string event) {
return (event == "replace" || event == "multiplay" ||
event == "gag" || event == "misc" ||
event == "discipline" || event == "showhelp" ||
event == "suspend");
} /* query_debounced() */
/**
* Check if the event is lords-only.
* @param event the name of the event
* @return nonzero if the event can only be added by lords
* @ignore
*/
protected int query_lord_only(string event) {
return ( member_array( event, _lordonly ) >= 0 ) ? 1 : 0;
} /* query_lord_only() */
/**
* Check if the event has come from the valid source.
* @param event the name of the event
* @param source the object trying to add the event
* @return nonzero if the event can be added by the given source
*/
int query_source_ok(string event, string source) {
// 0 means no check should be done; ({ }) means nobody can add it
string *reqd;
reqd = _sources[event]->add;
if(reqd == 0) {
return 1;
}
if(sizeof(reqd) == 0) {
return 0;
}
return member_array(source,reqd) >= 0;
} /* query_source_ok() */
/**
* Check if the request to delete an event came from the valid source.
* @param event the name of the event
* @param remover the object that tries to remove the event
*/
protected int query_deleter_ok(string event, object deleter) {
// 0 means no check should be done; ({ }) means nobody can delete it
string *reqd;
reqd = _sources[event]->delete;
if (reqd == 0) {
return 1;
}
if (sizeof(reqd) == 0) {
return 0;
}
return member_array(base_name(deleter),reqd) >= 0;
} /* query_deleter_ok() */
/**
* Check if the player is allowed to add the event.
* @param e the name of the event
* @param p the player who is trying to add it
* @return nonzero of the player is allowed to add the event
* @ignore
*/
protected int query_can_add(string e, object p) {
if(member_array(e,query_events()) < 0)
return 0;
if(!interactive(p))
return 1;
return !(query_lord_only(e) && !p->query_lord());
} /* query_can_add() */
/**
* Check if the player is allowed to perform delete operations.
* @param p the player who is trying to delete something
* @ignore
*/
protected int query_can_delete(string e, object p) {
if( !interactive(p) ) {
return 1;
}
if( e == "misc" || e == "replace" ) {
return master()->query_senior( p->query_name() ) ||
"/d/liaison/master"->query_deputy( p->query_name() );
}
return p->query_lord();
} /* query_can_delete() */
/**
* Check if the player is allowed to add and delete alt characters.
* @param p the player who is trying to add or delete alt(s)
* @ignore
*/
protected int query_can_handle_alts(object p) {
if( !interactive(p) ) {
return 1;
}
return master()->query_senior( p->query_name() ) ||
"/d/liaison/master"->query_deputy( p->query_name() );
} /* query_can_handle_alts() */
/**
* Check if we can debounce the event. Add a new event or modify
* the last event depending on whether it's debounceable or not.
* @param player the name of the player for which the database entry is added
* @param entry the database entry to be added
* @ignore
*/
protected void do_debouncing(string player, class dbentry entry) {
int n;
class dbentry last;
if (query_debounced(entry->event)) {
//tell_creator("pinkfish", "[playerinfo] Debouncing: %O.\n",entry);
n = sizeof(dossier->data);
if ( n ) {
last = dossier->data[ n - 1 ];
//tell_creator("pinkfish", "[playerinfo] Last: %O.\n",last);
if( entry->event == last->event &&
entry->creator == last->creator &&
entry->time - last->time <= DEBOUNCE_PERIOD)
{
// Merge the two events
// tell_creator("pinkfish", "[playerinfo] Merging events.\n");
last->comment += entry->comment;
last->time = entry->time;
if(last->extra != 0) {
if(entry->extra == 0) {
entry->extra = ({ });
}
last->extra += entry->extra;
}
//tell_creator("pinkfish", "[playerinfo] Result: %O.\n",last);
return;
}
}
}
//tell_creator("pinkfish", "[playerinfo] Not merging events.\n");
dossier->data += ({ entry });
return;
} /* do_debouncing() */
/**
* Check the currently loaded data for timed out entries and remove them.
* @ignore
*/
protected void do_timeout() {
function not_timed_out = function(class dbentry p)
{
int life = timeouts[p->event];
if(life == 0) // This event cannot be timed out
return 1;
// Time it out if its life period has expired
return local_time <= (p->time + life);
};
local_time = time();
dossier->data = filter(dossier->data, not_timed_out);
dossier->last_check = time();
} /* do_timeout() */
/**
* Load the data of the playerinfo object from its save file. If there's no
* file, create an empty dossier. Don't load anything if the data is
* already loaded.
* @param player the name of the player whose data is to be loaded
* @ignore
*/
protected void load_player(string player) {
string p = lower_case(player);
string fn = filename(p);
class playerinfo tmp;
mixed result;
_dossier_total++;
if ( !undefinedp(_dossier_cache[ player ] ) ) {
dossier = _dossier_cache[ player ];
_dossier_cache_hits++;
return;
}
if( dossier != 0 && dossier->name == p ) {
return; // Already have it here
}
if(file_size(fn) > 0) {
result = unguarded( (: restore_object, fn, 0 :) );
} else {
dossier = new(class playerinfo,
name: p,
last_check: time(),
alts: ({ }),
data: ({ }));
}
if (!classp(dossier)) {
dossier = new(class playerinfo,
name: p,
last_check: time(),
alts: ({ }),
data: ({ }));
}
if(sizeof(dossier) == 5) {
tmp = new(class playerinfo,
name: dossier->name,
last_check: dossier->last_check,
alts: copy(dossier->alts),
data: copy(dossier->data),
main_alt: dossier->main_alt,
old_alts: ({ }));
dossier = tmp;
}
if(!dossier->old_alts)
dossier->old_alts = ({ });
// Try and correct some of the damage caused by the last batch
// of changes which screwed up the alt system.
if(!correcting_alts) {
correct_alts_for( dossier->name );
}
_dossier_cache[ player ] = dossier;
} /* load_player() */
/**
* Save the data of the playerinfo object to its save file.
* @param player the name of the player whose data is to be saved
* @ignore
*/
protected void save_player(string player) {
if( time() - dossier->last_check >= TIMEOUT_PERIOD ) {
do_timeout();
}
unguarded( (: save_object, filename(player) :) );
} /* save_player() */
/**
* Remove the player's data file.
* @param player the name of the player
*/
void player_remove(string player) {
string alt, new_main;
string * alts;
if ( !player ) {
return;
}
player = lower_case( player );
// If this is a main alt, pick one of their alts and make them the
// main.
correcting_alts = 1;
load_player( player );
if ( sizeof( dossier->alts ) ) {
// Grab a list of alts from this dossier...
alts = copy( dossier->alts );
new_main = alts[0];
alts -= ({ new_main });
// ... Set up the new main player...
load_player( new_main );
dossier->main_alt = 0;
dossier->alts = uniq_array( alts + ({ player }) );
save_player( new_main );
// ... And make all the other alts point to the new main.
foreach( alt in alts ) {
load_player( alt );
dossier->main_alt = new_main;
save_player( alt );
}
}
correcting_alts = 0;
unguarded( (: rm, filename(player) :) );
clear_alerts_for(player);
} /* player_remove() */
/**
* Add a new entry to the player's database.
* @param source the creator or another object trying to add the event
* @param player the name of the player
* @param event event the name of the event to be added
* @param comment arbitrary comment text (more than one line is OK)
* @param extra arbitrary array of arbitrary objects (can be 0)
* @return nonzero if the entry was successfully added to the database
*/
int add_entry(object creator, string player, string event, string comment,
mixed *extra) {
class dbentry new_entry;
if(!query_can_add(event,creator))
return 0; // No permission to add this event
if(!query_source_ok(event,base_name(previous_object())))
return 0; // Wrong object trying to add this event
if(!PLAYER_HANDLER->test_user(lower_case(player)))
return 0; // No such player
if(comment[<1..<1] != "\n") {
comment += "\n";
}
load_player(player);
new_entry = new(class dbentry,
time: time(),
creator: capitalize(creator->query_name()),
event: event,
comment: (comment == 0 ? "" : comment),
extra: extra);
do_debouncing(player, new_entry);
save_player(player);
// Add the player to the alerts mapping (to be checked by the login
// handler which will dispatch warnings to currently online creators
// the next time the player logs in, until the event is acknowledged)
if( event == "alert" )
increment_alerts_for(player);
return 1;
} /* add_entry() */
/**
* Delete an entry from the playerinfo database.
* @param source the creator or another object trying to add the event
* @param player the name of the player
* @param event the name of the event of the entry being deleted
* @param n the index of the entry being deleted
* @return nonzero if the entry was successfully deleted
*/
int delete_entry(object creator, string player, string event, int n) {
int idx = n - 1;
class dbentry * data;
class dbentry fluff;
if( !query_can_delete( event, creator ) )
return 0;
if( !query_deleter_ok( event, previous_object() ) )
return 0; // Wrong object trying to delete this event
load_player(player);
if( ( idx < 0 ) || ( idx >= sizeof( dossier->data ) ) )
return 0;
fluff = dossier->data[idx];
if( fluff->event != event )
return 0;
data = copy( dossier->data );
data = data[0 .. (idx - 1)] + data[(idx + 1) .. <1];
dossier->data = data;
save_player(player);
log_file("DELETE", ctime(time()) + ": " + event + " added by " +
fluff->creator + "\n");
if( event == "alert" )
decrement_alerts_for(player);
return 1;
} /* delete_entry() */
/**
* Print the header of of the database report.
* @param source the creator who requested the report
* @param player the name of the player
* @ignore
*/
protected string query_header( string player ) {
string aka, alts, str;
string *tmp, *tmp2;
aka = alts = str = "";
if( sizeof( dossier->alts ) > 0 )
alts = " aka " +
query_multiple_short(map(dossier->alts,
(: CL_HEADER+capitalize($1)+CL_RESET :)));
if(sizeof(dossier->old_alts) > 0)
alts += " (and was " +
query_multiple_short(map(dossier->old_alts,
(: CL_HEADER+capitalize($1)+CL_RESET :))) + ")";
if( dossier->main_alt ) {
// Cleanup their alt list if the main_alt is deleted.
if(!PLAYER_HANDLER->test_user(dossier->main_alt)) {
fix_alts(player);
load_player(player);
}
aka = " (alt of " + CL_HEADER + capitalize(dossier->main_alt) + CL_RESET;
// Load up the 'main' player file and grab a list of all their alts,
// then reload this player's file.
load_player(dossier->main_alt);
tmp = copy(dossier->alts);
tmp2 = copy(dossier->old_alts);
if(!tmp) {
tmp = ({ });
dossier->alts = ({ });
save_player( dossier->name );
}
load_player(player);
tmp -= ({ player });
if(sizeof(tmp))
aka += ", aka " +
query_multiple_short(map(tmp, (: CL_HEADER+capitalize($1)+CL_RESET :)));
if( arrayp( tmp2 ) ) {
tmp2 -= ({ player });
if(sizeof(tmp2)) {
aka += " and was " +
query_multiple_short(map(tmp2, (: CL_HEADER+capitalize($1)+CL_RESET:)));
}
}
aka += ")";
}
return sprintf( "Report for: %s%s%s\n\n",
CL_HEADER + capitalize(player) + CL_RESET, alts, aka );
}
/**
* Print one entry of the dossier.
* @param source the creator who requested the report
* @param idx the index of the database entry to print
* @param e the database entry to print
* @ignore
*/
protected string query_entry( int idx, class dbentry e,
string display_name ) {
string date = ctime( e->time );
string creator = e->creator;
string event = e->event;
string *comments = explode( e->comment, "\n" );
string line, str;
int lines = 0;
if( display_name )
display_name = sprintf( "%-31s ",
"(" + CL_CREATOR + display_name + CL_RESET + ")" );
else
display_name = "";
str = sprintf( "%2d. %s%s %s%|14s%s (by %s%s%s)\n",
idx + 1, display_name, date, CL_EVENT, event, CL_RESET,
CL_CREATOR, creator, CL_RESET );
foreach( line in comments ) {
if( sizeof(line) != 0 ) {
str += sprintf( " %s\n", line );
lines++;
}
}
if( !lines ) {
str += sprintf( " (no comments)\n" );
}
return str;
}
/**
* Print all entries from the given player's dossier.
* @param source the creator who requested the report
* @param player the name of the player
*/
varargs string query_dossier( object source, string player, int lastn ) {
int i;
class dbentry * list;
string msg, str;
mapping log;
load_player(player);
str = query_header(player);
list = dossier->data;
//Backwards compatability.
if ( sizeof( dossier ) == NO_ACCESS_LOG ) {
dossier = new ( class playerinfo,
name: dossier->name,
last_check: dossier->last_check,
alts: dossier->alts,
data: dossier->data,
main_alt: dossier->main_alt,
old_alts: dossier->old_alts,
access_log: ([ ]) );
}
if ( mapp( dossier->access_log ) )
log = dossier->access_log;
else
log = ([ ]);
if( !lastn ||
sizeof(list) <= lastn ||
source->query_property(VERBOSE_PI) )
{
i = 0;
} else {
i = sizeof(list) - lastn;
msg = "%^RED%^NOTE:%^RESET%^ Only displaying this player's most "
"recent " + lastn + " entries. Use 'playerinfo "
+ player + " verbose' to see their entire dossier.\n";
str += msg + "\n";
}
for( ; i < sizeof( list ); i++ ) {
str += query_entry( i, list[i], 0 );
}
if( msg ) {
str += "\n" + msg;
}
//Filter week old entries.
log = filter( log, (: $2 > time() - ONE_WEEK :) );
//Add new ones!
if ( interactive( source ) )
log[ source->query_name() ] = time();
//Restore the access log.
dossier->access_log = log;
save_player( player );
return str;
}
/**
* Print all entries from the given player's dossier with the given event
* type.
* @param source the creator who requested the report
* @param player the name of the player
* @param event the name of the event
*/
string query_event( object source, string player, string event ) {
int i;
class dbentry *list;
string str;
load_player(player);
str = query_header(player);
list = dossier->data;
for( i = 0; i < sizeof(list); i++ ) {
if( list[i]->event == event )
str += query_entry( i, list[i], 0 );
}
return str;
}
/**
* Print all entries for this player and all alts, in chronological order.
* @param source the creator who requested the report
* @param player the name of the player
*/
string query_interleaved( object source, string player, string event ) {
class playerinfo * dossiers;
class dbentry * stuff;
string str;
string * alts;
int i, size, done, earliest, earliestt, count;
int * earliests;
load_player(player);
if( dossier->main_alt ) {
player = dossier->main_alt;
load_player(player);
}
str = query_header(player);
// Find all this player's alts
alts = ({ player }) + copy( dossier->alts );
size = sizeof(alts);
// If the player has no alts, just use the standard dossier display code
if( size == 1 ) {
if(event) {
str = query_event( source, player, event );
} else {
str = query_dossier( source, player );
}
return str;
}
// load up all of the dossiers locally
dossiers = allocate(size);
earliests = allocate(size);
for( i = 0; i < size; i++ ) {
load_player( alts[i] );
if( sizeof( dossier->data ) ) {
dossiers[i] = copy(dossier);
} else {
dossiers[i] = 0;
}
earliests[i] = 0;
}
// loop until there are no entries left to display.
count = 0;
while( !done ) {
done = 1;
earliestt = 0;
for( i = 0; i < size; i++ ) {
if( !dossiers[i] ) {
continue;
}
done = 0;
stuff = dossiers[i]->data;
if( !earliestt || stuff[ earliests[i] ]->time < earliestt ) {
earliest = i;
earliestt = stuff[ earliests[i] ]->time;
}
}
if(done) {
continue;
}
stuff = dossiers[earliest]->data;
if( !event ||
stuff[ earliests[earliest] ]->event == event )
{
str += query_entry( count, stuff[ earliests[earliest] ],
alts[earliest] );
count++;
}
earliests[earliest]++;
if( earliests[earliest] >= sizeof(stuff) ) {
dossiers[earliest] = 0;
}
}
return str;
}
/**
* Return which player this player is an alt of (if any).
* param player A players name
* return the name of the players main alt.
*/
string query_alt_of(string player) {
if(!PLAYER_HANDLER->test_user(player))
return 0;
load_player(player);
return dossier->main_alt;
}
string *query_alts(string player) {
if(!PLAYER_HANDLER->test_user(player))
return ({ });
load_player(player);
return dossier->alts;
}
/**
* Add an alt character name to this player's dossier. This function succeeds
* if both characters are not "main", or only one if them is "main". Both
* players will have their dossiers modified.
* @param player the name of the player
* @param alts the names of the alt characters to add
* @return a string describing the outcome of the function call
*/
string add_alt(object creator, string player, string alt) {
class dbentry new_entry;
int is_deleted;
string *alts, tmp;
if((base_name(previous_object()) != "/cmds/player/register") &&
!query_can_handle_alts(this_player()))
return "You are not allowed to add players' alts.\n";
alt = lower_case(alt);
if(!find_player(alt) && !PLAYER_HANDLER->test_user(alt)) {
if(!creator)
return "No such player: " + capitalize(alt) + ".\n";
if(file_size(filename(alt)) == -1)
return "No such player and no record for: " + capitalize(alt) + ".\n";
is_deleted = 1;
}
load_player(alt);
if(dossier->main_alt && PLAYER_HANDLER->test_user(dossier->main_alt) &&
!is_deleted)
return capitalize(alt) + " is already an alt of " +
capitalize(dossier->main_alt) + ".\n";
if(sizeof(dossier->alts)) {
if(is_deleted)
return capitalize(alt) + " already has alts.\n";
else {
// Since this player is deleted we'll add these alts & old_alts to
// the newly registered player.
alts = dossier->alts + dossier->old_alts;
}
}
player = lower_case(player);
if(!find_player(player) && !PLAYER_HANDLER->test_user(player))
return "No such player: "+ capitalize(player) +".\n";
correcting_alts = 1;
load_player(player);
if(dossier->main_alt == alt) {
correcting_alts = 0;
return capitalize(player) + " is already an alt of " +
capitalize(dossier->main_alt) + ".\n";
}
if(dossier->alts && member_array(alt, dossier->alts) != -1) {
correcting_alts = 0;
return capitalize(alt) + " is already an alt of " +
capitalize(player) + ".\n";
}
if(!dossier->alts)
dossier->alts = ({ });
dossier->alts += ({ alt });
if(alts) {
foreach(tmp in alts) {
if(PLAYER_HANDLER->test_user(tmp))
dossier->alts += ({ tmp });
else
dossier->old_alts += ({ tmp });
}
}
new_entry = new(class dbentry,
time: time(),
creator: creator ? capitalize(creator->query_name()) :
player,
event: "register",
comment: "Registered " + capitalize(alt) +
" as an alt.\n",
extra: 0);
do_debouncing(player, new_entry);
save_player(player);
load_player(alt);
dossier->main_alt = player;
dossier->alts = ({ });
dossier->old_alts = ({ });
new_entry = new(class dbentry,
time: time(),
creator: creator ? capitalize(creator->query_name()) :
player,
event: "register",
comment: "Registered as an alt of " + player + ".\n",
extra: 0);
do_debouncing(alt, new_entry);
save_player(alt);
correcting_alts = 0;
return "Added " + capitalize(alt) + " as an alt of " + capitalize(player) +
".\n";
}
/**
* Delete an alt character name from this player's dossier. Note that both
* players have their dossier modified.
* @param player the name of the player
* @param alts the name of the alt characters to delete
* @return a string describing the outcome of the function call
*/
string delete_alt(object creator, string player, string alt) {
string ret;
class dbentry new_entry;
if(!query_can_handle_alts(this_player()))
return "You are not allowed to delete players' alts.\n";
player = lower_case(player);
alt = lower_case(alt);
load_player(player);
if(!dossier->alts || member_array(alt, dossier->alts) == -1)
ret = capitalize(alt) + " was not an alt of " + capitalize(player);
else {
dossier->alts -= ({ alt });
new_entry = new(class dbentry,
time: time(),
creator: capitalize(creator->query_name()),
event: "register",
comment: "Removed " + capitalize(alt) + " as an alt.\n",
extra: 0);
do_debouncing(alt, new_entry);
save_player(player);
}
load_player(alt);
if(dossier->main_alt != player) {
if(ret)
return capitalize(alt) + " is not an alt of " + capitalize(player) +
".\n";
else
ret = capitalize(player) + " was not the main player for " +
capitalize(alt);
} else {
dossier->main_alt = 0;
dossier->alts = ({ });
dossier->old_alts = ({ });
new_entry = new(class dbentry,
time: time(),
creator: capitalize(creator->query_name()),
event: "register",
comment: "Removed as an alt of " + capitalize(player) +
".\n",
extra: 0);
do_debouncing(alt, new_entry);
save_player(alt);
}
if(ret)
return "Deleted " + capitalize(alt) + " from " + capitalize(player) +
"'s list of alts (" + ret + ").\n";
else
return "Deleted " + capitalize(alt) + " from " + capitalize(player) +
"'s list of alts.\n";
}
/**
* @return The alerts mapping.
*/
mapping query_alerts() {
if( !_alerts ) {
_alerts = ([ ]);
}
return _alerts;
} /* query_alerts() */
/**
* @param player Name of the player to query
* @return The number of alerts for that player
*/
int query_alerts_for( string player ) {
player = lower_case(player);
if( !_alerts ) {
_alerts = ([ ]);
}
return _alerts[player];
} /* query_alerts_for() */
/**
* @param player The name of the player
* @param idx The number of the event to check for alert status.
* @return 0 if the event is not an alert, 1 if it is.
*/
int is_alert( string player, int idx ) {
// if( !PLAYER_HANDLER->test_user( lower_case( player ) ) ) {
// return 0; // No such player
// }
load_player( player );
if( sizeof(dossier->data) < idx ) {
return 0;
}
return ( dossier->data[ idx - 1 ] )->event == "alert";
} /* is_alert() */
/*
* @param player The name of the player.
* @param event The type of event to change the alert to.
* @param idx The number of the event to acknowledge.
* @param update The event description
* @return 1 for success or 0 for failure.
*/
int acknowledge_alert( object creator, string player, string event,
int idx, string update, mixed * extra ) {
class dbentry entry;
string previnfo;
player = lower_case(player);
if( !query_can_add( event, creator ) )
return 0; // No permission to add this event
if( !query_source_ok( "alert", base_name( previous_object() ) ) )
return 0; // Wrong object trying to add this event
if( !query_source_ok( event, base_name( previous_object() ) ) )
return 0; // This object is not allowed to add this type of event.
if( !PLAYER_HANDLER->test_user(player) )
return 0; // No such player
if( member_array( event, keys(_sources) ) < 0 )
return 0; // No such event type
// Update the entry
idx--;
load_player( player );
entry = dossier->data[idx];
previnfo = sprintf( "Originally added by %s%s%s at %s:\n%s\n---\n",
CL_CREATOR, entry->creator, CL_RESET, ctime( entry->time ),
entry->comment );
entry->time = time();
entry->creator = capitalize( creator->query_name() );
entry->event = lower_case( event );
entry->comment = previnfo + update;
dossier->data[idx] = entry;
if( event != "alert" ) {
decrement_alerts_for(player);
}
save_player( player );
return 1;
} /* acknowledge_alert() */
/**
* @param player The name of the player.
*/
void clear_alerts_for( string player ) {
player = lower_case(player);
if( !_alerts ) {
_alerts = ([ ]);
}
map_delete( _alerts, player );
save_handler_data();
} /* clear_alerts_for() */
/**
* @param player The name of the player.
* @return The updated number of alerts for that player.
*/
int increment_alerts_for( string player ) {
player = lower_case(player);
if( !_alerts ) {
_alerts = ([ ]);
}
if( !PLAYER_HANDLER->test_user(player) ) {
return 0;
}
if( undefinedp( _alerts[player] ) ) {
_alerts[player] = 1;
} else {
_alerts[player] = _alerts[player] + 1;
}
save_handler_data();
return _alerts[player];
} /* increment_alerts_for() */
/**
* @param player The name of the player.
* @return The updated number of alerts for that player.
*/
int decrement_alerts_for( string player ) {
player = lower_case(player);
if( !_alerts ) {
_alerts = ([ ]);
}
if( undefinedp( _alerts[player] ) ) {
return 0;
}
_alerts[player] = _alerts[player] - 1;
if(_alerts[player] <= 0 ) {
map_delete( _alerts, player );
}
save_handler_data();
return _alerts[player];
} /* int decrement_alerts_for() */
/**
* @return A list of the currently-online players who have unacknowledged alerts
*/
string * query_online_alerts() {
if ( !_alerts ) {
_alerts = ([ ]);
return ({ });
}
return filter( keys(_alerts), (: find_player($1) :) );
} /* query_online_alerts() */
/**
* @ignore yes
* Added by Shrike, extended by Ceres. It fixes up the handling of alts.
*/
private void correct_alts_for(string player) {
string main;
string *alts, *tmp, alt;
if(!player)
return;
player = lower_case(player);
correcting_alts = 1;
// Load the player's dossier...
load_player(player);
main = dossier->main_alt;
alts = copy(dossier->alts);
// if(this_player()->query_name("ceres"))
// write("Processing: " + player + " (" + main + ")\n");
if(main && !no_recurse) {
// non-main characters should not have alts.
if(sizeof(alts)) {
dossier->alts = ({ });
save_player(player);
}
// The alt & the main char must exist.
if((!PLAYER_HANDLER->test_user(player) ||
!PLAYER_HANDLER->test_user(main))) {
correcting_alts = 0;
no_recurse = 1;
correct_alts_for(main);
load_player(player); // reload the original
}
return;
}
// This is the main alt and it nolonger exists.
if(!PLAYER_HANDLER->test_user(player)) {
// No alts so we're done.
if(!sizeof(alts)) {
if(this_player()->query_name("ceres"))
write("No alts for : " + player + "\n");
correcting_alts = 0;
return;
}
// Need to pick a new main alt.
tmp = filter(alts, (: PLAYER_HANDLER->test_user($1) :));
if(sizeof(tmp)) {
main = tmp[0];
}
}
// We've picked a new main so clear our alts list & mark as as
// having been an alt of the new main alt.
if(main) {
dossier->main_alt = main;
dossier->alts = ({ });
save_player(player);
}
// ... Load up the new main player and add to their alts as required...
if(main)
load_player(main);
if(sizeof(alts)) {
tmp = filter(alts, (: !find_player($1) &&
!PLAYER_HANDLER->test_user($1) :));
tmp = uniq_array(dossier->old_alts + tmp);
} else
alts = ({ });
alts = uniq_array(alts + dossier->alts) - ({ main });
alts = filter(alts, (: find_player($1) || PLAYER_HANDLER->test_user($1) :));
// If we have a new main char, the alts or old alts have changed
// then we have some tidying up to do.
if(main && (alts != dossier->alts || tmp != dossier->old_alts)) {
dossier->alts = copy(alts);
dossier->old_alts = copy(tmp);
save_player(main);
// ... Make sure all of this guy's alts point to them as the main...
foreach(alt in alts) {
if(dossier->main_alt != main) {
load_player(alt);
dossier->main_alt = main;
save_player(alt);
}
}
// ... Make sure all of this guy's alts point to them as the main...
foreach(alt in tmp) {
if(dossier->main_alt != main) {
load_player(alt);
dossier->main_alt = main;
save_player(alt);
}
}
}
// ... and restore the original player.
load_player(player);
correcting_alts = 0;
}
void reregister_parent( string player ) {
string main;
string * alts;
if ( !player ) {
return;
}
player = lower_case(player);
correcting_alts = 1;
// Load the player's dossier...
load_player( player );
main = dossier->main_alt;
if ( main ) {
load_player(main);
alts = dossier->alts;
if ( !alts || !sizeof( alts ) ) {
dossier->alts = ({ player });
} else if ( member_array( player, alts ) == -1 ) {
dossier->alts = alts + ({ player });
} else {
return;
}
save_player( main );
}
} /* reregister_parent() */
/**
* @ignore yes
*
* Register a players refresh. This is called by the refresh handler.
*/
void player_refreshed(mixed player, int totally) {
class dbentry new_entry;
string comment;
if(objectp(player))
player = player->query_name();
switch (totally) {
case PARTIAL_REFRESH:
comment = "They did a partial refresh.";
break;
case TOTAL_REFRESH:
comment = "They did a total and complete refresh. (Even refreshed their "
"breath)";
break;
default:
comment = "They did some weird sort of unknown refresh.";
break;
}
//
// Slip an event into their playerinfo
//
load_player(player);
new_entry = new(class dbentry,
time: time(),
creator: "Refresh Handler",
event: "refresh",
comment: comment,
extra: 0);
do_debouncing(player, new_entry);
save_player(player);
}
/**
* @ignore yes
*
* Register a players deletion. This is called by the refresh handler.
*/
void player_deleted(mixed player, int deleted) {
class dbentry new_entry;
string *tmp, *tmp2, alt, main;
if(objectp(player))
player = player->query_name();
// No record, then lets not create one eh?
if(file_size(filename(player)) == -1)
return;
clear_alerts_for(player);
load_player(player);
new_entry = new(class dbentry,
time: time(),
creator: "Refresh Handler",
event: "delete",
comment: "Player deleted",
extra: 0);
do_debouncing(player, new_entry);
save_player(player);
// move us from the alts list to the old_alts list for our main char.
if(dossier->main_alt) {
main = dossier->main_alt;
dossier->main_alt = 0;
save_player(player);
load_player(main);
dossier->alts -= ({ player });
dossier->old_alts += ({ player });
save_player(main);
return;
}
// This was the main char. So we must pick a new main, transfer
// all alts into it and then update the main_alt for all the other alts.
if(dossier->alts && sizeof(dossier->alts)) {
// choose a new main.
tmp = filter(dossier->alts, (: PLAYER_HANDLER->test_user($1) :));
if(sizeof(tmp))
main = tmp[0];
tmp2 = dossier->old_alts;
// load and setup the new main.
load_player(main);
dossier->main_alt = 0;
if(sizeof(tmp) > 1)
dossier->alts = copy(tmp[1..]);
dossier->old_alts = copy(tmp2 + ({ player }));
save_player(main);
// change all the other alts over to use this new main.
foreach(alt in dossier->alts) {
load_player(alt);
dossier->main_alt = main;
save_player(alt);
}
return;
}
}
/** @ignore yes */
void player_created(string player) {
if(file_size(filename(player)) == -1)
return;
load_player(player);
if(dossier->main_alt) {
dossier->main_alt = 0;
dossier->alts = ({ });
save_player(player);
}
return;
}
/** @ignore yes */
void fix_alts(mixed player) {
string *alts, alt, main;
if(objectp(player))
player = player->query_name();
// No record, not much we can do.
if(file_size(filename(player)) == -1)
return;
load_player(player);
if(dossier->main_alt) {
main = dossier->main_alt;
load_player(dossier->main_alt);
if(!PLAYER_HANDLER->test_user(dossier->main_alt)) {
if(sizeof(alts))
main = alts[0];
}
} else {
main = player;
}
// Make sure we're in the alts list and main isn't.
// Put in some stricter type checking to deal with the situation
// when ->alts or ->old_alts is 0
alts = ({});
if( dossier->alts ) {
alts = alts + dossier->alts;
}
if( dossier->old_alts ) {
alts = alts + dossier->old_alts;
}
alts = uniq_array( alts + ({ player }));
alts -= ({ main });
// load and setup the new main.
dossier->main_alt = 0;
dossier->alts = filter(alts, (: PLAYER_HANDLER->test_user($1) :));
dossier->old_alts = filter(alts, (: !PLAYER_HANDLER->test_user($1) :));
// change all the other alts over to use this new main.
foreach(alt in alts) {
load_player(alt);
dossier->main_alt = main;
dossier->alts = 0;
dossier->old_alts = 0;
save_player(alt);
}
}
void reset() {
if ( sizeof( _dossier_cache ) > CACHE_SIZE ) {
_dossier_cache = ([ ]);
}
} /* reset() */
string query_access_history( string player ) {
mapping history;
string ret = "";
load_player(player);
// if ( sizeof( dossier ) == NO_ACCESS_LOG )
// return "This player record has not been accessed since Tue May 27 01:29:16 2003.\n";
history = dossier->access_log;
ret += sprintf( "Access log for player %s:\n", capitalize( player ) );
foreach( string name in sort_array( keys( history ),
(: $(history)[$2] - $(history)[$1] :) ))
ret += sprintf( "%12s:\t%s.\n", capitalize(name),
query_time_string( time() - history[name], 2 ) + " ago");
return ret;
}
mixed *stats() {
return ({
({ "cache hits", _dossier_cache_hits }),
({ "total requests", _dossier_total }),
({ "cache hit %", to_int(_dossier_cache_hits * 100.0 /
_dossier_total) }),
({ "current cache size", sizeof( _dossier_cache ) })
});
} /* stats() */