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/
/**
 * This is the handler which deals with parcels that are being sent
 * from player to player, or, as it may be, from post office to
 * post office.
 *
 * These are the things I want to do here:<br>
 * - Each PO should have coordinates.
 * <br>
 * - One should be able to pick which PO to send the parcel to, and
 *   the cost of posting a parcel should depend on the weight of the
 *   parcel and the distance between this PO and the destination PO.
 * <br>
 * - It should take time for the parcel to arrive at the destination
 *   based on the distance between the starting PO and destination PO.
 * <br>
 * - If no-one has collected the parcel within 30 days from the arrival,
 *   send it back to the sender, if there is one.  Then if the sender
 *   doesn't come to collect it within a month, discard the parcel.
 * <br>
 * - We are going to wrap the things in a special envelope/packet,
 *   and use that as a container, which will disappear when unwrapped.
 * <br>
 * @see /std/shops/parcel_office.c
 * @author Sandoz, 2002.
 */

#define AUTO_LOAD_H       "/global/player/auto_load"

/** This is the minimum shipping cost. */
#define MIN_SHIPPING_COST 2000
/** This value is used to round all the cost calculations. */
#define ROUND_DENOMINATOR 100

#define DAY               (60*60*24)
#define MONTH             (30*DAY)

#define FAIL(x)           do_fail_mess( npc, x )
#define MS(x,y)           (string)MONEY_H->money_value_string(x,y)

#define PARCEL_SAVE_DIR   SAVE_DIR "/parcels/"
#define SHIP_DIR          PARCEL_SAVE_DIR "shipping/"

/**
 * This class stores the data for a single parcel.
 * @member from the name of the player the parcel is from
 * @member from_office the parcel office the parcel was sent from
 * @member autoload the autoload info of the parcel
 * @member expiry_time the time the parcel should be either
 * discarded or returned to the sender
 * @member returned whether or not the parcel is being returned
 */
class parcel_data {
    string from;
    string from_office;
    mixed autoload;
    int expiry_time;
    int returned;
}

/**
 * This class stores the data for a single parcel that is being shipped.
 * @member to the person this parcel is for
 * @member to_office the post office this parcel was sent to
 * @member due the time the parcel should reach its destination
 */
class shipping_data {
    string to;
    string to_office;
    int due;
}

/**
 * This class stores the data for a single post office.
 * You can make a restricted office by setting the restricted flag,
 * this is good for testing purposes, so that players can't send
 * parcels to offices you're still in the middle of setting up.
 * @member coords the coordinates of the office
 * @member data the mapping of parcel expiry times for players
 * @member restricted a flag that can be set to make the office restricted
 */
class office_data {
    int *coords;
    mapping data;
    int restricted;
}

nosave string cur_player, cur_office;
nosave class parcel_data *player_data;

private mapping offices, shipping;
private int check_time;

private void do_hourly_check();

/** @ignore */
private void create() {
    function f;

    if( !unguarded( (: dir_exists, PARCEL_SAVE_DIR :) ) )
        unguarded( (: mkdir, PARCEL_SAVE_DIR :) );

    if( !unguarded( (: dir_exists, SHIP_DIR :) ) )
        unguarded( (: mkdir, SHIP_DIR :) );

    if( unguarded( (: file_exists, PARCEL_SAVE_DIR+"main.o" :) ) )
        unguarded( (: restore_object, PARCEL_SAVE_DIR+"main.o" :) );

    if( !mapp(offices) )
        offices = ([ ]);

    if( !mapp(shipping) )
        shipping = ([ ]);

    f = function( function func ) {
            call_out( func, 3600, func );
            do_hourly_check();
        };

    call_out( f, 60, f );

} /* create() */

/** @ignore */
private void save_me() {
    unguarded( (: save_object, PARCEL_SAVE_DIR+"main.o" :) );
} /* save_me() */

/**
 * This method returns the mapping of all post office specific data.
 * @return a mapping which contains the office specific data
 */
mapping query_offices() { return offices;}

/**
 * This method returns the array of all restricted post offices.
 * @return all restricted post offices
 */
string *query_restricted_offices() {
    return keys( filter( offices, (: $2->restricted :) ) );
} /* query_restricted_offices() */

/**
 * This method returns the array of all unrestricted post offices.
 * @return all unrestricted post offices
 */
string *query_unrestricted_offices() {
    return keys( filter( offices, (: !$2->restricted :) ) );
} /* query_restricted_offices() */

/**
 * This method returns the mapping of all shipment specific data.
 * @return a mapping which contains the shipment specific data
 */
mapping query_shipping() { return shipping; }

/** @ignore yes */
private int compare_coords( int *i, int *j ) {
    return i[0] == j[0] && i[1] == j[1] && i[2] == j[2];
} /* compare_coords() */

/**
 * This method queries whether or not the input string is a valid
 * post office.
 * @param name the name of the post office to test
 */
int query_post_office( string name ) { return !undefinedp( offices[name] ); }

/**
 * This method adds a post office into the handler.
 * It will make sense to have a 'main' post office with
 * 0, 0, 0 coords, relative to which all the other offices'
 * coordinates should be set.
 * @param name the name of the post office
 * @param coords the coordinates of the post office
 * @param restricted whether or not this is a restricted office
 * @return 1 upon success, 0 upon failure
 * @see remove_office()
 * @see change_coords()
 */
int add_office( string name, int *coords, int restricted ) {
    if( !adminp( previous_object(-1) ) ) {
        printf("Sorry, only admin can add post offices.\n");
        return 0;
    }

    if( !stringp(name) || !sizeof(name) ) {
        printf("Invalid office name.\n");
        return 0;
    }

    if( query_post_office(name) ) {
        printf("A post office by that name already exists.\n");
        return 0;
    }

    if( !pointerp(coords) || sizeof(coords) != 3 ||
        sizeof( filter( coords, (: !intp($1) :) ) ) ) {
        printf("Invalid coordinates; must be an integer array of three "
            "elements.\n");
        return 0;
    }

    if( sizeof( filter( values(offices),
        (: compare_coords( $1->coords, $2 ) :), coords ) ) ) {
        printf("An office with those coordinates already exists.\n");
        return 0;
    }

    offices[name] = new( class office_data,
                         coords     : coords,
                         data       : ([ ]),
                         restricted : restricted );
    save_me();

    return 1;

} /* add_office() */

/**
 * This method changes a post office's coordinates.
 * @param name the name of the post office
 * @param coords the new coordinates of the post office
 * @return 1 upon success, 0 upon failure
 * @see remove_office()
 */
int change_coords( string name, int *coords ) {
    if( !adminp( previous_object(-1) ) ) {
        printf("Sorry, only admin can change post office coordinates.\n");
        return 0;
    }

    if( !stringp(name) || !sizeof(name) ) {
        printf("Invalid office name.\n");
        return 0;
    }

    if( !query_post_office(name) ) {
        printf("There is no post office by that name.\n");
        return 0;
    }

    if( !pointerp(coords) || sizeof(coords) != 3 ||
        sizeof( filter( coords, (: !intp($1) :) ) ) ) {
        printf("Invalid coordinates; must be an integer array of three "
            "elements.\n");
        return 0;
    }

    if( compare_coords( offices[name]->coords, coords ) ) {
        printf("That office already has those coordinates.\n");
        return 0;
    }

    if( sizeof( filter( values(offices),
        (: compare_coords( $1->coords, $2 ) :), coords ) ) ) {
        printf("An office with those coordinates already exists.\n");
        return 0;
    }

    offices[name]->coords = coords;
    save_me();

    return 1;

} /* change_coords() */

/**
 * This method sets the restricted flag of the post office.
 * @param name the name of the office to set the restricted flag for
 * @param restrict whether or not we should be a restricted office
 * @see add_office()
 * @see remove_office()
 * @see change_coords()
 */
int restrict_office( string name, int restrict ) {
    if( !adminp( previous_object(-1) ) ) {
        printf("Sorry, only admin can change the restricted flag of post "
            "offices.\n");
        return 0;
    }

    if( !stringp(name) || !sizeof(name) ) {
        printf("Invalid office name.\n");
        return 0;
    }

    if( !query_post_office(name) ) {
        printf("There is no post office by that name.\n");
        return 0;
    }

    offices[name]->restricted = restrict;

    save_me();
    return 1;

} /* restrict_office() */

/**
 * This method changes a post office's coordinates.
 * @param name the name of the post office
 * @return 1 upon success, 0 upon failure
 * @see add_office()
 */
int remove_office( string name ) {
    if( !adminp( previous_object(-1) ) ) {
        printf("Sorry, only admin can remove post offices.\n");
        return 0;
    }

    if( !query_post_office( name) ) {
        printf("There is no post office by that name.\n");
        return 0;
    }

    map_delete( offices, name );
    save_me();

    return 1;

} /* remove_office() */

/** @ignore yes */
string normalize_name( string name ) {
    int i;

    name = lower_case(name);

    for( i = 0; i < strlen(name); i++ ) {
        if( name[i] == ''' ) {
            name = name[0..i-1] + name[i+1..];
            i--;
            continue;
        }

        if( name[i] < 'a' || name[i] > 'z')
            name[i] = '_';
    }

    return implode( explode( name, "_") - ({ 0, "" }), "_");

} /* normalize_name() */

/** @ignore yes */
string query_real_office_name( string name ) {
    string bit, tmp;

    if( query_post_office( name ) )
        return name;

    tmp = lower_case(name);

    foreach( bit in keys( offices ) )
        if( tmp == lower_case(bit) )
            return bit;

    return name;

} /* query_real_office_name() */

private string query_office_save_dir( string office ) {
    office = normalize_name( office );
    return PARCEL_SAVE_DIR+office+"/";
} /* query_office_save_fir() */

/** @ignore yes */
private void reset_player_data() {
    cur_office = 0;
    cur_player = 0;
    player_data = 0;
} /* reset_player_data() */

/** @ignore yes */
private void save_player_data() {
    string dir;

    if( !cur_player || !cur_office || !player_data ) {
        error("Attempted to save with no cur_player, cur_office or "
            "player_data.\n");
        return;
    }

    dir = query_office_save_dir(cur_office);

    if( !unguarded( (: dir_exists, dir :) ) )
        unguarded( (: mkdir, dir :) );

    unguarded( (: write_file, dir+cur_player+".o",
        save_variable(player_data), 1 :) );

} /* save_player_data() */

/** @ignore yes */
private void load_player_data( string office, string player ) {
    string data;

    if( cur_office == office && cur_player == player )
        return;

    data = query_office_save_dir(office)+player+".o";

    if( unguarded( (: file_exists, data :) ) ) {
        data = unguarded( (: read_file, data :) );
        if( stringp(data) )
            player_data = restore_variable(data);
    }

    if( !pointerp(player_data) )
        player_data = ({ });

    cur_player = player;
    cur_office = office;

} /* load_player_data() */

/**
 * This method calculates the distance between two post offices
 * based on their coordinates.
 * @param from the starting post office
 * @param to the destination post office
 * @return the distance between the post offices
 */
int query_distance( string from, string to ) {
    int *i, *j;

    i = offices[from]->coords;
    j = offices[to]->coords;

    return 10 * sqrt( ( ( i[0] - j[0] ) * ( i[0] - j[0] ) ) +
                      ( ( i[1] - j[1] ) * ( i[1] - j[1] ) ) +
                      ( ( i[2] - j[2] ) * ( i[2] - j[2] ) ) );

} /* query_distance() */

/**
 * This method calculates the shipping cost of a parcel from one
 * post office to another based on their coordinates and the
 * weight of the parcel.  This will be rounded up using the
 * ROUND_DENOMINATOR define.
 * @param ob the object we are trying to ship
 * @param from the starting post office
 * @param to the destination post office
 * @return the shipping cost
 */
int query_shipping_cost( object ob, string from, string to ) {
    int i, j, k;

    if( k = query_distance( from, to ) ) {
        if( k < MIN_SHIPPING_COST )
            k = MIN_SHIPPING_COST;
        if( j = ( k % ROUND_DENOMINATOR ) )
            k += ROUND_DENOMINATOR - j;
    }

    i = ( ob->query_complete_weight() + 1 ) * 20;

    if( i < MIN_SHIPPING_COST )
        i = MIN_SHIPPING_COST;

    if( j = ( i % ROUND_DENOMINATOR ) )
        i += ROUND_DENOMINATOR - j;

    return i + k;

} /* query_shipping_cost() */

/**
 * This method calculates the shipping time from one
 * post office to another based on their coordinates.
 * @param from the starting post office
 * @param to the destination post office
 * @return the shipping time in seconds, or 0 if there
 * are no such post offices
 */
int query_shipping_time( string from, string to ) {
    return 60 * query_distance( from, to );
} /* query_shipping_time() */

/** @ignore yes */
private int do_fail_mess( object servant, string mess ) {
    if( servant ) {
        servant->do_command("'"+mess );
        return notify_fail("");
    }

    add_failed_mess( mess+"\n");
    return 0;

} /* do_fail_mess() */

/**
 * This method deals with the actual shipping command.
 * If the command is successful, event_parcel_sent()
 * will be called on the post office room, that should
 * give a message to the player about success.
 * The arguments passed to the function are the name of the
 * person who we sent the parcel to, the name of the post
 * office we sent the parcel to, and the time they should
 * receive the parcel.
 * Note : the objects we send MUST have query_is_postage_parcel()
 * return 1 on them, so that we can know they are properly
 * packed packages.
 * @param obs the things we are trying to send
 * @param to the person the parcel is to
 * @param where the name of the post office to send it to
 * @param from the post office we are sending the parcel from
 * @param loc the location for currency calculations
 * @param npc the NPC serving the player, if there is any
 * @return 1 upon success, 0 upon failure
 */
int do_send( object *obs, string to, string where, string from,
             string loc, object npc ) {
    class parcel_data data;
    int ship, id, cost;
    string mess;
    object ob;

    if( !PO->query_parcel_office() ) {
        add_failed_mess("You can only send parcels from post offices.\n");
        return 0;
    }

    if( sizeof(obs) > 1 )
        return FAIL("You can only send one parcel at a time.");

    if( !query_post_office( where ) ||
        ( !creatorp(TP) && offices[where]->restricted ) )
        return FAIL( where+" doesn't appear to be a valid post office.  "
            "Please 'list offices' to get a list of post offices you can "
            "send parcels to.");

    ob = obs[0];
    to = lower_case(to);

    if( to == TP->query_name() )
        return FAIL("You can't send "+ob->the_short()+" to yourself, silly!");

    if( !PLAYER_H->test_user( to ) )
        return FAIL("There is no player by that name!");

    if( MULTIPLAYER_H->member_denied_parcel( TP->query_name() ) )
        return FAIL("You are not allowed to send parcels to anyone.");

    if( MULTIPLAYER_H->member_denied_parcel( to ) )
        return FAIL( CAP(to)+" is not allowed to receive parcels from "
            "anyone.");

    if( !ob->query_is_postage_parcel() )
        return FAIL( ob->the_short()+" "+( query_group(ob) ?
            "do not appear to be proper parcels." :
            "does not appear to be a proper parcel.")+"  You can only send "
            "things in packets, sealed envelopes or similar containers.");

    cost = query_shipping_cost( ob, from, where );

    if( TP->query_value_in(loc) < cost )
        return FAIL("You cannot afford to send "+ob->the_short()+" to "+
            where+" for "+CAP(to)+".  It would cost "+MS( cost, loc )+".");

    ship = query_shipping_time( from, where ) + time();

    mess = sprintf("%'* '=*s\n*%' '=*s\n", 78, "", 76, "*");
    mess += sprintf("%*-s*\n", 76, sprintf("*%+=*s%-=*s", 10, "TO : ", 76,
        CAP(to)+" "+where ) );
    mess += sprintf("%*-s*\n", 76, sprintf("*%+=*s%-=*s", 10, "FROM : ", 76,
        TP->query_cap_name()+" "+from ) );
    mess += sprintf("*%' '=*s\n%'* '=*s\n", 76, "*", 78, "");

    obs->add_read_mess(mess);

    data = new( class parcel_data,
                from        : TP->query_name(),
                from_office : from,
                autoload    : AUTO_LOAD_H->create_auto_load( ({ ob }), 0 ),
                expiry_time : ship + MONTH,
                returned    : 0 );

    if( ob->move("/room/rubbish") ) {
        ob->remove_read_mess(mess);
        return FAIL("You cannot let go of "+ob->the_short()+" for some "
            "reason.");
    }

    while( !undefinedp( shipping[++id] ) );

    if( !unguarded( (: write_file, SHIP_DIR+id+".o",
        save_variable(data), 1 :) ) ) {
        ob->remove_read_mess(mess);
        ob->move(TP);
        return FAIL("Something went wrong with sending "+
            ob->the_short()+" to "+CAP(to)+", sorry.");
    }

    if( npc )
        npc->do_command("'That will be "+MS( cost, loc )+", please.");
    else
        tell_object( TP, "That will cost "+MS( cost, loc )+".\n");

    TP->pay_money( MONEY_H->create_money_array( cost, loc ), loc );

    mess = ( npc ? npc->the_short() : "the parcel clerk");

    tell_object( TP, "You hand the money to "+mess+".\n");
    tell_room( ENV(TP), TP->the_short()+" $V$0=hands,hand$V$ some money to "+
        mess+".\n", TP );

    shipping[id] = new( class shipping_data,
                        to        : to,
                        to_office : where,
                        due       : ship );

    save_me();

    PO->event_parcel_sent( to, where, ship );

    return 1;

} /* do_send() */

/**
 * This method prints the cost of sending a specific parcel to
 * somewhere for the player.
 * @param obs the objects to get the shipping costs for
 * @param from the office we are sending from
 * @param to the destination we are sending to
 * @param loc the location for currency calculations
 * @param npc the servant NPC, if there is one
 * @return 1 upon success, 0 upon failure
 */
int do_cost( object *obs, string from, string to, string loc, object npc ) {
    object ob;

    if( !PO->query_parcel_office() ) {
        add_failed_mess("You can only send parcels from post offices.\n");
        return 0;
    }

    if( sizeof(obs) > 1 )
        return FAIL("You can only check the shipping costs of one parcel "
            "at a time.");

    if( !query_post_office( to ) ||
        ( !creatorp(TP) && offices[to]->restricted ) )
        return FAIL( to+" doesn't appear to be a valid post office.  "
            "Please 'list offices' to get a list of post offices you can "
            "send parcels to.");

    ob = obs[0];

    if( !ob->query_is_postage_parcel() )
        return FAIL( ob->the_short()+" "+( query_group(ob) ?
            "do not appear to be proper parcels." :
            "does not appear to be a proper parcel.")+"  You can only send "
            "things in packets, sealed envelopes or similar containers.");

    to = "It would cost "+MS( query_shipping_cost( ob, from, to ), loc )+" "+
         "to send "+ob->the_short()+" to "+to+".";

    if( npc )
        npc->do_command("'"+to );
    else
        tell_object( TP, to+"\n");

    return 1;

} /* do_cost() */

/** @ignore yes */
private int sort_classes( int one, int two ) {
    if( one > two )
        return 1;

    if( one < two )
        return -1;

    return 0;

} /* sort_classes() */

/** @ignore yes */
private void log_and_save_current_player() {
    string file;
    class parcel_data parcel;

    map_delete( offices[cur_office]->data, cur_player );
    file = query_office_save_dir(cur_office)+cur_player+".o";

    if( !sizeof( player_data ) ) {
        log_file("PARCELS", "%s - removed - %s - no data left.\n",
            ctime(time()), file );

        unguarded( (: rm, file :) );

        reset_player_data();

    } else {
        log_file("PARCELS", "%s - saved - %s - %i parcels.\n",
            ctime(time()), file, sizeof(player_data) );

        foreach( parcel in player_data )
            if( !offices[cur_office]->data[cur_player] ||
                offices[cur_office]->data[cur_player] > parcel->expiry_time )
                offices[cur_office]->data[cur_player] = parcel->expiry_time;

        save_player_data();
    }

} /* log_and_save_current_player() */

/**
 * This method collects a parcel for a player, it will collect the
 * parcel with the earliest expiry date.
 * This is done one parcel at a time so that players wouldn't
 * fumble stuff.
 * If the command is successful, event_parcel_collected()
 * will be called on the post office room, that should
 * give a message to the player about success.
 * The arguments passed to the function are an array of
 * objects that should be moved to the player (or a shelf/their
 * env if they're too burdened) and the number of parcels that
 * are left for the player.
 * @param office the name of the parcel office we are at
 * @param npc the servant NPC, if we have one
 * @return 1 if there are parcels, and 0 if there are not
 */
int do_collect( string office, object npc ) {
    object *obs;
    string name;
    class parcel_data parcel;

    if( !PO->query_parcel_office() ) {
        add_failed_mess("You can only collect parcels at post offices.\n");
        return 0;
    }

    name = (string)TP->query_name();

    if( MULTIPLAYER_H->member_denied_parcel( name ) )
        return FAIL("You are not allowed to collect parcels.\n");

    if( undefinedp( offices[office]->data[name] ) )
        return FAIL("There are no parcels waiting for you here.");

    load_player_data( office, name );

    if( !sizeof(player_data) )
        return FAIL("Something has gone wrong with storing your parcel data, "
            "please contact a creator immediately.");

    player_data = sort_array( player_data,
        (: sort_classes( $1->expiry_time, $2->expiry_time ) :) );

    parcel = player_data[0];
    player_data = player_data[1..];

    obs = AUTO_LOAD_H->load_auto_load_to_array( parcel->autoload, TP );
    PO->event_parcel_collected( obs, sizeof(player_data) );

    if( npc && parcel->expiry_time < time() )
        npc->do_command("'You are lucky, "+TP->query_cap_name()+", for we "
            "were just about to throw away that parcel.");

    log_file("PARCELS", "%s - %s collected a parcel from %s.\n",
        ctime(time()), CAP(name), office );

    log_and_save_current_player();

    save_me();

    return 1;

} /* do_collect() */

/**
 * This method prints all the post offices they can send parcels
 * to for a player.
 * @param npc the servant NPC, if there is one
 * @return always returns 1
 */
int do_list( object npc ) {
    string *str;

    if( !creatorp(TP) )
        str = query_unrestricted_offices();
    else
        str = keys(offices);

    str = sort_array( str, 1 );

    if( npc ) {
        npc->do_command("'The post offices to which you can send parcels "
            "from here are the following - "+query_multiple_short( str )+
            ".");
    } else {
        printf("The post offices to which you can send parcels from here "
            "are:\n%-*#s\n", (int)TP->query_cols(), implode( str, "\n") );
    }

    return 1;

} /* do_list() */

/** @ignore yes */
private void do_office_check( string office ) {
    int expiry_time;
    string player;
    class parcel_data data;

    foreach( player, expiry_time in offices[office]->data ) {
        if( expiry_time < time() ) {
            load_player_data( office, player );
            foreach( data in player_data ) {
                if( data->expiry_time < time() ) {
                    if( data->returned ) {
                        MAIL_H->do_mail_message( player, "the parcel clerk",
                            "Parcel Discard Notification", "", "Dear "+
                            CAP(player)+",\n\nWe are sorry to inform you "
                            "that the parcel at "+office+" returned to you "
                            "from "+data->from_office+" has been discarded."
                            "\n\nThe Parcel Clerk,\n"+office, 0, 0 );

                        log_file("PARCELS", "%s - discarded returned parcel "
                            "in %s - %s.\n", ctime(time()), office,
                            CAP(player) );

                        player_data -= ({ data });
                        continue;
                    }

                    if( PLAYER_H->test_user(data->from) ) {
                        int ship, id;
                        class parcel_data parcel;

                        MAIL_H->do_mail_message( player, "the parcel clerk",
                            "Parcel Return Notification", "", "Dear "+
                            CAP(player)+",\n\nWe are sorry to inform you "
                            "that the parcel at "+office+" sent to you from "+
                            data->from_office+" by "+CAP(data->from)+" has "
                            "been returned to the sender.\n\n"
                            "The Parcel Clerk,\n"+office, 0, 0 );

                        ship = query_shipping_time( office,
                            data->from_office ) + time();

                        parcel = new( class parcel_data,
                                      from        : player,
                                      from_office : office,
                                      autoload    : data->autoload,
                                      expiry_time : ship + MONTH,
                                      returned    : 1 );

                        while( !undefinedp( shipping[++id] ) );

                        unguarded( (: write_file, SHIP_DIR+id+".o",
                            save_variable(parcel), 1 :) );

                        shipping[id] = new( class shipping_data,
                                            to        : data->from,
                                            to_office : data->from_office,
                                            due       : ship );

                        log_file("PARCELS", "%s - returned parcel from %s to "
                            "%s (%s).\n", ctime(time()), office,
                            data->from_office, CAP(data->from) );

                        player_data -= ({ data });
                        continue;
                    }

                    MAIL_H->do_mail_message( player, "the parcel clerk",
                        "Parcel Discard Notification", "", "Dear "+
                        CAP(player)+",\n\nWe are sorry to inform you "
                        "that the parcel at "+office+" sent to you from "+
                        data->from_office+" by "+CAP(data->from)+" has "
                        "been discarded.\n\n"
                        "The Parcel Clerk,\n"+office, 0, 0 );

                    log_file("PARCELS", "%s - discarded parcel from %s (%s) "
                        "to %s (%s) - no user to return to.\n", ctime(time()),
                        data->from_office, CAP(data->from), office,
                        CAP(player) );

                    player_data -= ({ data });
                    continue;
                }
            }

            log_and_save_current_player();

        }
    }

} /* do_office_check() */

/** @ignore yes */
private void do_hourly_check() {
    int id;
    class shipping_data data;
    class parcel_data parcel;

    foreach( id, data in shipping ) {
        if( !PLAYER_H->test_user( data->to ) ) {
            unguarded( (: rm, SHIP_DIR+id+".o" :) );
            map_delete( shipping, id );
            continue;
        }

        if( data->due < time() ) {
            string player, office;

            player = data->to;
            office = data->to_office;

            parcel = restore_variable( unguarded( (: read_file,
                SHIP_DIR+id+".o" :) ) );

            load_player_data( office, player );

            player_data += ({ parcel });

            if( !offices[office]->data[player] ||
                offices[office]->data[player] > parcel->expiry_time )
                offices[office]->data[player] = parcel->expiry_time;

            save_player_data();

            if( !parcel->returned ) {
                MAIL_H->do_mail_message( player, "the parcel clerk",
                    "Parcel Deposit Notification", "", "Dear "+
                    CAP(player)+",\n\nPlease come to the "+office+" parcel "
                    "counter to collect the parcel which has been sent to "
                    "you from "+parcel->from_office+" by "+
                    CAP(parcel->from)+".  If you have not collected the "
                    "parcel by "+ctime(parcel->expiry_time)+", it will be "+
                    ( PLAYER_H->test_user(parcel->from) ?
                    "returned to the sender" : "discarded")+".\n\n"
                    "The Parcel Clerk,\n"+office, 0, 0 );
                log_file("PARCELS", "%s - parcel from %s (%s) arrived at "
                    "%s (%s).\n", ctime(time()), parcel->from_office,
                    CAP(parcel->from), office, CAP(player) );
            } else {
                MAIL_H->do_mail_message( player, "the parcel clerk",
                    "Parcel Deposit Notification", "", "Dear "+
                    CAP(player)+",\n\nPlease come to the "+office+" parcel "
                    "counter to collect the parcel which has been returned "
                    "to you from "+parcel->from_office+".  If you have not "
                    "collected the parcel by "+ctime(parcel->expiry_time)+", "
                    "it will be discarded.\n\n"
                    "The Parcel Clerk,\n"+office, 0, 0 );
                log_file("PARCELS", "%s - parcel to %s (%s) arrived back to "
                    "%s (%s) - RETURNED.\n", ctime(time()),
                    parcel->from_office, CAP(parcel->from), office,
                    CAP(player) );
            }

            unguarded( (: rm, SHIP_DIR+id+".o" :) );
            map_delete( shipping, id );
        }
    }

    if( check_time < time() ) {
        check_time = time() + DAY;
        map( keys(offices), (: do_office_check($1) :) );
    }

    save_me();

} /* do_hourly_check() */

/**
 * This method should be called by the refresh handler each
 * time a player refreshes, or is deleted.  This will delete
 * all parcels meant for the player.
 * @param player the player being refreshed or deleted
 */
void player_refreshed( string player ) {
    class shipping_data data;
    string office;
    int id, flag;

    if( PO != find_object(REFRESH_H) )
        return;

    foreach( id, data in shipping ) {
        if( data->to == player ) {
            flag = 1;
            unguarded( (: rm, SHIP_DIR+id+".o" :) );
            map_delete( shipping, id );
        }
    }

    foreach( office in keys(offices) ) {
        if( !undefinedp( offices[office]->data[player] ) ) {
            flag = 1;
            unguarded( (: rm, query_office_save_dir(office)+player+".o" :) );
            map_delete( offices[office]->data, player );
        }
    }

    if( cur_player == player )
        reset_player_data();

    save_me();

    if( flag )
        log_file("PARCELS", "%s - %s refreshed.\n", ctime(time()),
            CAP(player) );

} /* player_refreshed() */

/** @ignore yes */
int query_check_time() { return check_time; }

/** @ignore yes */
void reset_check_time() {
    if( adminp( previous_object(-1) ) )
        check_time = 0;
} /* reset_check_time() */

/** @ignore yes */
void dest_me() { destruct(TO); }

/** @ignore yes */
mixed stats() {
    return ({
        ({"post offices", sizeof(offices) }),
        ({"restricted", sizeof( query_restricted_offices() ) }),
        ({"shipping", sizeof(shipping) }),
        ({"next check", ctime(check_time)+" ("+check_time+")" }),
    });
} /* stats() */