skylib_fluffos_v3/
skylib_fluffos_v3/bin/
skylib_fluffos_v3/bin/db/
skylib_fluffos_v3/fluffos-2.9-ds2.04/
skylib_fluffos_v3/fluffos-2.9-ds2.04/ChangeLog.old/
skylib_fluffos_v3/fluffos-2.9-ds2.04/Win32/
skylib_fluffos_v3/fluffos-2.9-ds2.04/compat/
skylib_fluffos_v3/fluffos-2.9-ds2.04/compat/simuls/
skylib_fluffos_v3/fluffos-2.9-ds2.04/include/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/clone/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/command/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/data/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/etc/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/include/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/inherit/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/inherit/master/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/log/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/compiler/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/efuns/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/operators/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/u/
skylib_fluffos_v3/fluffos-2.9-ds2.04/tmp/
skylib_fluffos_v3/fluffos-2.9-ds2.04/windows/
skylib_fluffos_v3/mudlib/
skylib_fluffos_v3/mudlib/cmds/
skylib_fluffos_v3/mudlib/cmds/admin/
skylib_fluffos_v3/mudlib/cmds/guild-race/
skylib_fluffos_v3/mudlib/cmds/living/broken/
skylib_fluffos_v3/mudlib/cmds/player/group_cmds/
skylib_fluffos_v3/mudlib/cmds/playtester/
skylib_fluffos_v3/mudlib/d/admin/
skylib_fluffos_v3/mudlib/d/admin/room/
skylib_fluffos_v3/mudlib/d/admin/room/we_care/
skylib_fluffos_v3/mudlib/d/admin/save/
skylib_fluffos_v3/mudlib/d/admin/text/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/buildings/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/map/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/roads/
skylib_fluffos_v3/mudlib/d/learning/chars/
skylib_fluffos_v3/mudlib/d/learning/functions/
skylib_fluffos_v3/mudlib/d/learning/handlers/
skylib_fluffos_v3/mudlib/d/learning/help_topics/
skylib_fluffos_v3/mudlib/d/learning/help_topics/npcs/
skylib_fluffos_v3/mudlib/d/learning/help_topics/objects/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rcs_demo/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rcs_demo/RCS/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/crowd/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/situations/
skylib_fluffos_v3/mudlib/d/learning/save/
skylib_fluffos_v3/mudlib/d/learning/school/
skylib_fluffos_v3/mudlib/d/learning/school/add_sc/
skylib_fluffos_v3/mudlib/d/learning/school/characters/
skylib_fluffos_v3/mudlib/d/learning/school/general/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/basic_commands/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/edtutor/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/unix_tutor/
skylib_fluffos_v3/mudlib/d/learning/school/items/
skylib_fluffos_v3/mudlib/d/learning/school/npc_school/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/room_basic/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/situations/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/terrain_tutor/
skylib_fluffos_v3/mudlib/d/learning/text/
skylib_fluffos_v3/mudlib/d/liaison/
skylib_fluffos_v3/mudlib/d/mudlib/
skylib_fluffos_v3/mudlib/d/mudlib/changes/
skylib_fluffos_v3/mudlib/d/playtesters/
skylib_fluffos_v3/mudlib/d/playtesters/effects/
skylib_fluffos_v3/mudlib/d/playtesters/handlers/
skylib_fluffos_v3/mudlib/d/playtesters/items/
skylib_fluffos_v3/mudlib/d/sage/
skylib_fluffos_v3/mudlib/doc/
skylib_fluffos_v3/mudlib/doc/creator/
skylib_fluffos_v3/mudlib/doc/driver/
skylib_fluffos_v3/mudlib/doc/driver/efuns/arrays/
skylib_fluffos_v3/mudlib/doc/driver/efuns/buffers/
skylib_fluffos_v3/mudlib/doc/driver/efuns/calls/
skylib_fluffos_v3/mudlib/doc/driver/efuns/compile/
skylib_fluffos_v3/mudlib/doc/driver/efuns/filesystem/
skylib_fluffos_v3/mudlib/doc/driver/efuns/floats/
skylib_fluffos_v3/mudlib/doc/driver/efuns/functions/
skylib_fluffos_v3/mudlib/doc/driver/efuns/general/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mappings/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mixed/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mudlib/
skylib_fluffos_v3/mudlib/doc/driver/efuns/numbers/
skylib_fluffos_v3/mudlib/doc/driver/efuns/parsing/
skylib_fluffos_v3/mudlib/doc/login/
skylib_fluffos_v3/mudlib/doc/lpc/basic_manual/
skylib_fluffos_v3/mudlib/doc/lpc/intermediate/
skylib_fluffos_v3/mudlib/doc/new/add_command/
skylib_fluffos_v3/mudlib/doc/new/events/
skylib_fluffos_v3/mudlib/doc/new/handlers/
skylib_fluffos_v3/mudlib/doc/new/living/race/
skylib_fluffos_v3/mudlib/doc/new/living/spells/
skylib_fluffos_v3/mudlib/doc/new/object/
skylib_fluffos_v3/mudlib/doc/new/player/
skylib_fluffos_v3/mudlib/doc/new/room/guild/
skylib_fluffos_v3/mudlib/doc/new/room/outside/
skylib_fluffos_v3/mudlib/doc/new/room/storeroom/
skylib_fluffos_v3/mudlib/doc/object/
skylib_fluffos_v3/mudlib/doc/playtesters/
skylib_fluffos_v3/mudlib/doc/policy/
skylib_fluffos_v3/mudlib/doc/weapons/
skylib_fluffos_v3/mudlib/global/
skylib_fluffos_v3/mudlib/global/creator/
skylib_fluffos_v3/mudlib/handlers/
skylib_fluffos_v3/mudlib/include/casino/
skylib_fluffos_v3/mudlib/include/cmds/
skylib_fluffos_v3/mudlib/include/effects/
skylib_fluffos_v3/mudlib/include/npc/
skylib_fluffos_v3/mudlib/include/room/
skylib_fluffos_v3/mudlib/include/shops/
skylib_fluffos_v3/mudlib/net/daemon/
skylib_fluffos_v3/mudlib/net/daemon/chars/
skylib_fluffos_v3/mudlib/net/inherit/
skylib_fluffos_v3/mudlib/net/obj/
skylib_fluffos_v3/mudlib/net/obj/BACKUPS/
skylib_fluffos_v3/mudlib/obj/amulets/
skylib_fluffos_v3/mudlib/obj/armours/plate/
skylib_fluffos_v3/mudlib/obj/b_day/
skylib_fluffos_v3/mudlib/obj/clothes/transport/horse/
skylib_fluffos_v3/mudlib/obj/faith/symbols/
skylib_fluffos_v3/mudlib/obj/fungi/
skylib_fluffos_v3/mudlib/obj/gatherables/
skylib_fluffos_v3/mudlib/obj/instruments/
skylib_fluffos_v3/mudlib/obj/media/
skylib_fluffos_v3/mudlib/obj/misc/player_shop/
skylib_fluffos_v3/mudlib/obj/monster/godmother/
skylib_fluffos_v3/mudlib/obj/monster/transport/
skylib_fluffos_v3/mudlib/obj/rings/
skylib_fluffos_v3/mudlib/obj/scabbards/
skylib_fluffos_v3/mudlib/obj/spells/
skylib_fluffos_v3/mudlib/obj/stationery/
skylib_fluffos_v3/mudlib/obj/stationery/envelopes/
skylib_fluffos_v3/mudlib/obj/toys/
skylib_fluffos_v3/mudlib/obj/vessels/
skylib_fluffos_v3/mudlib/obj/weapons/axes/
skylib_fluffos_v3/mudlib/obj/weapons/chains/
skylib_fluffos_v3/mudlib/obj/weapons/maces/BACKUPS/
skylib_fluffos_v3/mudlib/save/autodoc/
skylib_fluffos_v3/mudlib/save/book_handler/
skylib_fluffos_v3/mudlib/save/books/history/calarien/
skylib_fluffos_v3/mudlib/save/mail/
skylib_fluffos_v3/mudlib/save/new_soul/data/
skylib_fluffos_v3/mudlib/save/parcels/
skylib_fluffos_v3/mudlib/save/playerinfo/
skylib_fluffos_v3/mudlib/save/players/d/
skylib_fluffos_v3/mudlib/save/players/s/
skylib_fluffos_v3/mudlib/save/random_names/
skylib_fluffos_v3/mudlib/save/random_names/data/
skylib_fluffos_v3/mudlib/save/terrains/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_desert/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_grassy_field/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_mountain/
skylib_fluffos_v3/mudlib/save/todo_lists/
skylib_fluffos_v3/mudlib/secure/
skylib_fluffos_v3/mudlib/secure/cmds/admin/
skylib_fluffos_v3/mudlib/secure/cmds/lord/
skylib_fluffos_v3/mudlib/secure/config/
skylib_fluffos_v3/mudlib/secure/handlers/autodoc/
skylib_fluffos_v3/mudlib/secure/handlers/intermud/
skylib_fluffos_v3/mudlib/secure/include/global/
skylib_fluffos_v3/mudlib/secure/save/
skylib_fluffos_v3/mudlib/secure/save/handlers/
skylib_fluffos_v3/mudlib/secure/std/
skylib_fluffos_v3/mudlib/secure/std/classes/
skylib_fluffos_v3/mudlib/secure/std/modules/
skylib_fluffos_v3/mudlib/std/creator/
skylib_fluffos_v3/mudlib/std/dom/
skylib_fluffos_v3/mudlib/std/effects/
skylib_fluffos_v3/mudlib/std/effects/external/
skylib_fluffos_v3/mudlib/std/effects/fighting/
skylib_fluffos_v3/mudlib/std/effects/magic/
skylib_fluffos_v3/mudlib/std/effects/magic/BACKUPS/
skylib_fluffos_v3/mudlib/std/effects/other/BACKUPS/
skylib_fluffos_v3/mudlib/std/effects/priest/
skylib_fluffos_v3/mudlib/std/effects/room/
skylib_fluffos_v3/mudlib/std/environ/
skylib_fluffos_v3/mudlib/std/guilds/
skylib_fluffos_v3/mudlib/std/guilds/old/
skylib_fluffos_v3/mudlib/std/languages/
skylib_fluffos_v3/mudlib/std/liquids/
skylib_fluffos_v3/mudlib/std/npc/
skylib_fluffos_v3/mudlib/std/npc/goals/
skylib_fluffos_v3/mudlib/std/npc/goals/basic/
skylib_fluffos_v3/mudlib/std/npc/goals/misc/
skylib_fluffos_v3/mudlib/std/npc/plans/
skylib_fluffos_v3/mudlib/std/npc/plans/basic/
skylib_fluffos_v3/mudlib/std/npc/types/
skylib_fluffos_v3/mudlib/std/npc/types/helper/
skylib_fluffos_v3/mudlib/std/npcs/
skylib_fluffos_v3/mudlib/std/outsides/
skylib_fluffos_v3/mudlib/std/races/shadows/
skylib_fluffos_v3/mudlib/std/room/basic/BACKUPS/
skylib_fluffos_v3/mudlib/std/room/basic/topography/
skylib_fluffos_v3/mudlib/std/room/controller/
skylib_fluffos_v3/mudlib/std/room/inherit/topography/
skylib_fluffos_v3/mudlib/std/room/topography/area/
skylib_fluffos_v3/mudlib/std/room/topography/iroom/
skylib_fluffos_v3/mudlib/std/room/topography/milestone/
skylib_fluffos_v3/mudlib/std/shadows/curses/
skylib_fluffos_v3/mudlib/std/shadows/disease/
skylib_fluffos_v3/mudlib/std/shadows/fighting/
skylib_fluffos_v3/mudlib/std/shadows/healing/
skylib_fluffos_v3/mudlib/std/shadows/magic/
skylib_fluffos_v3/mudlib/std/shadows/poison/
skylib_fluffos_v3/mudlib/std/shadows/room/
skylib_fluffos_v3/mudlib/std/shops/controllers/
skylib_fluffos_v3/mudlib/std/shops/objs/
skylib_fluffos_v3/mudlib/std/shops/player_shop/
skylib_fluffos_v3/mudlib/std/socket/
skylib_fluffos_v3/mudlib/std/soul/d/
skylib_fluffos_v3/mudlib/std/soul/e/
skylib_fluffos_v3/mudlib/std/soul/i/
skylib_fluffos_v3/mudlib/std/soul/j/
skylib_fluffos_v3/mudlib/std/soul/k/
skylib_fluffos_v3/mudlib/std/soul/l/
skylib_fluffos_v3/mudlib/std/soul/n/
skylib_fluffos_v3/mudlib/std/soul/o/
skylib_fluffos_v3/mudlib/std/soul/q/
skylib_fluffos_v3/mudlib/std/soul/r/
skylib_fluffos_v3/mudlib/std/soul/u/
skylib_fluffos_v3/mudlib/std/soul/v/
skylib_fluffos_v3/mudlib/std/soul/y/
skylib_fluffos_v3/mudlib/std/soul/z/
skylib_fluffos_v3/mudlib/std/stationery/
skylib_fluffos_v3/mudlib/w/
skylib_fluffos_v3/mudlib/w/default/
skylib_fluffos_v3/mudlib/w/default/armour/
skylib_fluffos_v3/mudlib/w/default/clothes/
skylib_fluffos_v3/mudlib/w/default/item/
skylib_fluffos_v3/mudlib/w/default/npc/
skylib_fluffos_v3/mudlib/w/default/room/
skylib_fluffos_v3/mudlib/w/default/weapon/
skylib_fluffos_v3/mudlib/www/
skylib_fluffos_v3/mudlib/www/java/
skylib_fluffos_v3/mudlib/www/secure/
skylib_fluffos_v3/mudlib/www/secure/lpc/advanced/
skylib_fluffos_v3/mudlib/www/secure/lpc/intermediate/
skylib_fluffos_v3/win32/
/**
 * 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>

// This is where our save files go
#define PLAYERINFO_SAVE_DIR         "/save/playerinfo"
#define SAVE_FILE                   PLAYERINFO_SAVE_DIR "/handler_data"

// Originators of function calls. Checked for security reasons
#define WAND               "/obj/misc/wand"
#define MULTIPLAY          "/handlers/multiplayer"
#define GAGGER             "/obj/misc/gagger"
#define GAG                "/cmds/creator/gag"
#define UNGAG              "/cmds/creator/ungag"
#define METEOR             "/cmds/creator/meteor"
#define FRY                "/cmds/admin/fry"
#define SKULLWARN          "/cmds/admin/skullwarn"
#define WHOOP              "/cmds/admin/whoop"
#define SWAT               "/cmds/admin/swat"
#define HAMMER             "/cmds/admin/hammer"
#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   "/obj/misc/pk_token"
#define REFRESH_HANDLER    "/handlers/refresh"
#define PRISON             "/d/sur/beta/prison/dungeon"
#define FETCH_COMMAND      "/cmds/creator/fetch"
#define REARRANGE_COMMAND  "/cmds/guild-race/rearrange"

// 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*10)

// This is the interval between consecutive checks for timeouts.
// One day is suffucuent.
#define TIMEOUT_PERIOD  (60*60*24)

// 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 void   print_header(object source, string player);
protected void   print_entry(object source, int idx, class dbentry e);
void             print_dossier(object source, string player);
void             print_event(object source, string player, string event);
string           add_alts(object creator, string player, string *alts);
string           delete_alts(object creator, string player, string* alts);
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 );


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;

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() {
    _sources = ([
        "cheat":        new(class source,
                            add : ({ }),
                            delete : ({ DELETE_COMMAND })),
        "discipline":   new(class source,
                            add : ({ ADD_COMMAND, PRISON }),
                            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 : ({ GAGGER, GAG, UNGAG }),
                            delete : ({ DELETE_COMMAND })),
        "harassment":   new(class source,
                            add : ({ ADD_COMMAND }),
                            delete : ({ DELETE_COMMAND })),
        "meteor":       new(class source,
                            add : ({ METEOR}),
                            delete : ({ DELETE_COMMAND })),
        "fry":          new(class source,
                            add : ({ FRY}),
                            delete : ({ DELETE_COMMAND })),
        "skullwarn":    new(class source,
                            add : ({ SKULLWARN}),
                            delete : ({ DELETE_COMMAND })),
        "whoop":        new(class source,
                            add : ({ WHOOP}),
                            delete : ({ DELETE_COMMAND })),
        "swat":         new(class source,
                            add : ({ SWAT}),
                            delete : ({ DELETE_COMMAND })),
        "misc":         new(class source,
                            add : ({ ADD_COMMAND, PATRICIAN_PALACE,
                                     REARRANGE_COMMAND }),
                            delete : ({ DELETE_COMMAND })),
        "multiplay":    new(class source,
                            add : ({ MULTIPLAY }),
                            delete : ({ DELETE_COMMAND })),
        "replace":      new(class source,
                            add : ({ WAND, ADD_COMMAND, FETCH_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 }))
    ]);

    _alerts = ([ ]);
    _lordonly = ({ });

    seteuid("Room");
    load_handler_data();

} /* create() */

// Return the mapping of event timeouts
mapping query_timeouts() {
    return timeouts;
} /* query_timeouts() */

/**
 * 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",PLAYERINFO_SAVE_DIR,p[0],p);
}

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

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

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

    // for testing purposes only. remember to remove this later!!
    if( creatorp(deleter))
        return 1;

    reqd = _sources[event]->delete;

    if (reqd == 0) {
        return 1;
    }
    if (sizeof(reqd) == 0) {
        return 0;
    }
    return member_array(base_name(deleter),reqd) >= 0;
}

/**
 * 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) && !lordp(p));
}

/**
 * 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;
    // for testing purposes only. remember to remove this later!!
    if( creatorp(p))
        return 1;
    if(e == "misc" || e == "replace")
        return seniorp(p->query_name());
    return lordp(p);
}

/**
 * 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 seniorp(p->query_name());
}

/**
 * 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 != 0) {
            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;
}

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

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

    if(dossier != 0 && dossier->name == p)
        return;                // Already have it here

    if(file_size(fn) > 0) {
        unguarded( (: restore_object, fn :) );
    } else {
        dossier = new(class playerinfo,
            name:       p,
            last_check: time(),
            alts:       ({ }),
            data:       ({ }));
    }
}

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

/**
 * Remove the player's data file.
 * @param player the name of the player
 */
void player_remove(string player) {
   unguarded( (: rm, filename(player) :) );
   clear_alerts_for(player);
}

/**
 * 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(PO) ) )
        return 0;              // Wrong object trying to add this event
    if( !PLAYER_H->test_user( lower_case(player) ) )
        return 0;              // No such player

    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;

   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;
   if( dossier->data[idx]->event != event )
      return 0;

   data = copy( dossier->data );
   data = data[0 .. (idx - 1)] + data[(idx + 1) .. <1];
   dossier->data = data;
   save_player(player);
   if( event == "alert" )
      decrement_alerts_for(player);

   return 1;
}

/**
 * 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 void print_header(object source, string player) {
    string  alt, aka = "", alts = "";
    int     first = 1;

    if(sizeof(dossier->alts) > 0) {
        foreach(alt in dossier->alts) {
            if(first) first = 0; else alts = alts + ", ";
            alts = alts + CL_HEADER + capitalize(alt) + CL_RESET;
        }
        alts = " aka " + alts;
    }
    if(dossier->main_alt != 0)
        aka = " (alt of " + CL_HEADER + capitalize(dossier->main_alt) +
            CL_RESET + ")";
    tell_object(source,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 void print_entry(object source, int idx, class dbentry e) {
    string  date        = ctime(e->time);
    string  creator     = e->creator;
    string  event       = e->event;
    string  *comments   = explode(e->comment,"\n");
    string  line;
    int     lines = 0;
    int     cols;

    tell_object( source, sprintf("%d. %-26s %s%|20s%s (added by %s%s%s)\n",
        idx+1,date, CL_EVENT, event, CL_RESET, CL_CREATOR, creator,
        CL_RESET ) );

    cols = (int)source->query_cols() || 79;

    foreach( line in comments ) {
        if( sizeof(line) ) {
            tell_object( source, indent( line+"\n", 4, cols ) );
            lines++;
        }
    }

    if( !lines )
        tell_object( source, sprintf("    (no comments)\n") );

} /* print_entry() */

/**
 * Print all entries from the given player's dossier.
 * @param source the creator who requested the report
 * @param player the name of the player
 */
void print_dossier(object source, string player) {
    int             i;
    class dbentry   *list;
//  class playerinfo migrated;

    load_player(player);
/*
    migrated = new(class playerinfo,
        name:       dossier->name,
        last_check: dossier->last_check,
        alts:       dossier->alts,
        data:       dossier->data,
        main_alt:   0);
    dossier = migrated;
    save_player(player);
*/
    print_header(source,player);
    list = dossier->data;
    for ( i = 0; i < sizeof( list ); i++ ) {
        print_entry(source,i,list[i]);
    }
}

/**
 * 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
 */
void print_event(object source, string player, string event) {
    int             i;
    class dbentry   *list;

    load_player(player);
    print_header(source,player);
    list = dossier->data;
    for (i = sizeof(list)-1; i >= 0; i--) {
      if (list[i]->event == event) {
        print_entry(source,i,list[i]);
      }
    }
}

/**
 * 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_alts(object creator, string player, string *alts) {
    string  p1 = lower_case(player);
    string  *p2 = uniq_array(map(alts, (: lower_case($1) :)));
    string  alt, *to_add, result;

    if(!query_can_handle_alts(this_player()))
        return "You are not allowed to add players' alts.  \n";
    if( !PLAYER_H->test_user(p1) )
        return "No such player: "+p1+".  \n";   // No such player
    foreach(alt in p2) {
        if( !PLAYER_H->test_user(alt) )
            return "No such player: "+alt+".  \n";  // No such player
    }

    load_player(p1);
    if(dossier->main_alt != 0) {
        return capitalize(p1)+" is already an alt of "+
            capitalize(dossier->main_alt);
    }

    p2 -= dossier->alts;
    p2 -= ({ p1 });
    to_add = ({ });

    foreach(alt in p2) {
        load_player(alt);
        if(dossier->main_alt == 0 && sizeof(dossier->alts) == 0) {
            dossier->main_alt = p1;
            dossier->alts = ({ });
            save_player(alt);
            to_add += ({ alt });
        }
    }

    if(sizeof(to_add) == 0)
        return "Couldn't add any alts.  \n";

    load_player(p1);
    if(dossier->alts == 0)
        dossier->alts = ({ });  // We are lazy...
    dossier->alts = uniq_array(dossier->alts + to_add);  // I LOVE MAPPINGS!
    save_player(p1);

    // Get ready for an example of a REALLY crazy code!
    // (just for reference, all it does is formatting a list of names
    // in a nice capitalized, comma-separated way :) ).
    result = "Added "+
        implode(map(to_add, (: capitalize($1) :)),", ")+
        " to "+capitalize(p1)+"'s list of alts.  \n";
    if(sizeof(p2) != sizeof(to_add))
        result += "Couldn't add: "+
            implode(map(p2-to_add, (: capitalize($1) :)),", ")+".  \n";

    return result;
}

/**
 * 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_alts(object creator, string player, string* alts) {
    string  p1 = lower_case(player);
    string  *p2 = map(alts, (: lower_case($1) :));
    string  alt, *to_delete, result;

    if( !query_can_handle_alts( TP ) )
        return "You are not allowed to delete players' alts.  \n";
    if( !PLAYER_H->test_user(p1) )
        return "No such player: "+p1+".  \n";   // No such player
    foreach( alt in p2 ) {
        if( !PLAYER_H->test_user(alt) )
            return "No such player: "+alt+".  \n";  // No such player
    }

    load_player(p1);
    if(sizeof(dossier->alts) == 0)
        return capitalize(p1)+" doesn't have any alts.  \n";

    to_delete = ({ });
    foreach(alt in p2) {
        load_player(alt);
        if(dossier->main_alt == p1) {
            dossier->main_alt = 0;
            dossier->alts = ({ });
            save_player(alt);
            to_delete += ({ alt });
        }
    }

    if(sizeof(to_delete) == 0)
        return "Couldn't delete any alts.  \n";

    load_player(p1);
    if(dossier->alts == 0)
        dossier->alts = ({ });
    dossier->alts = uniq_array(dossier->alts - to_delete);  // (see above)
    save_player(p1);

    // Same crazy code as above.
    result = "Deleted "+
        implode(map(to_delete, (: capitalize($1) :)),", ")+
        " from "+capitalize(p1)+"'s list of alts.  \n";
    if(sizeof(p2) != sizeof(to_delete))
        result += "Couldn't delete: "+
            implode(map(p2-to_delete, (: capitalize($1) :)),", ")+".  \n";

    return result;
}
/**
 *  @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_H->test_user( lower_case( player ) ) )
       return 0;

   load_player( player );

   return idx > 1 && sizeof(dossier->data) > --idx &&
        (dossier->data[idx])->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( !PLAYER_H->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;
   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_H->test_user(player) )
       return 0;

    _alerts[player]++;

   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;

   if( --_alerts[player] <= 0 ) {
       map_delete( _alerts, player );
       save_handler_data();
       return 0;
   }

   save_handler_data();
   return _alerts[player];

} /* int decrement_alerts_for() */

/**
 * @return list of the currently-online players who have unacknowledged alerts
 */
string *query_online_alerts() {
   return filter( keys( query_alerts() ),
     (: member_array( $1, users()->query_name() ) !=-1 :) );
} /* query_online_alerts() */