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 group handler.  It does all the handling of
 * player-run adventuring groups, but not alone.  It's
 * accompanied by the group commands, effect and shadow.
 * For paths to these files, consult /include/group_handler.h
 *
 * This system owes its design first and foremost to the irreplaceable
 * Ceres who wrote the original system.  Next come all the valuable
 * Creators who shared their knowledge of LPC.  Last but not least
 * are the countless playtesters who dedicated their time and worked
 * out all (or most of) the kinks and provided feedback and suggestions.
 *
 * So give 'em all a big hug.
 *
 * @author Tape
 * @started February 99
 */

#include <group_handler.h>
#include <broadcaster.h>
#include <login_handler.h>

int _loaded;
int _groups_formed;

mapping _groups;

class group
{
   int start_time;               // Time() when group was created
   string short;                 // Group's short description
   string leader_name;           // Group's leader's name
   object leader;                // Group's leader
   object *members;              // Group's members
   object *invited;              // People invited to group
}


int is_group( string group );
int is_member( string name, object person );
int is_invited( string name, object person );
object *invitations_to( string name );
string query_group_short( string name );
object *members_of( string name );
object leader_of( string name );
string short_to_name( string short );
int create_group( string name );
int remove_group( string name );
int add_invite( string name, object person, int flag );
int remove_invite( string name, object person );
int add_member( string name, object person );
int remove_member( string name, object person );
varargs int set_leader( string name, object person, object appointer );
void notify_group( string name, object broadcaster, mixed message );
varargs void disband_group( string name, mixed message );
varargs object shuffle_new_leader( string group, int way, object *exclude );
void leader_goes_linkdead( string player, string event_type );
void handle_group_follow( string group, object who, object *what,
   int unfollow, int silent );
void broadcast_to_groups( string *name, string message );
string *query_groups();

/** @ignore yes */
void create()
{
   _groups = ([ ]);
   _loaded = time();
   _groups_formed = 0;
} /* create() */


/** @ignore yes */
void dest_me() {

   string name;

   broadcast_to_groups( 0, "%^BOLD%^WARNING%^RESET%^: The group handler is being destructed."
      "  All active groups will be disbanded.  It should be possible to "
      "recreate the group almost immediately afterwards.  If not, please "
      "file a bug report for the \"group\" command." );
      
   foreach( name in query_groups() ) {
      disband_group( name, 0 );
   }

} /* dest_me() */


/** @ignore yes */
void stats_please() {
   printf( "The handler was loaded on %s.  Since then, "
      "%i groups have been formed.\n", ctime( _loaded ),
      _groups_formed );
} /* stats_please() */


/**
 * This returns the names of all groups currently in the database.
 * @return the names of all groups in database
 */
string *query_groups() {
   return keys( _groups );
} /* query_groups() */


/**
 * This function checks if such a group exists at the moment.
 *
 * @param group the name of the group
 * @return 1 if the group exists, 0 otherwise
 */
int is_group( string group ) {
   return !undefinedp( _groups[ group ] );
} /* is_group() */


/**
 * This function allows you to check whether a person is a member
 * of a group.
 *
 * @param name the name of the group
 * @param person the person you want to check for membership
 *
 * @return 1 if he is a member, 0 if he isn't (or the group doesn't exist)
 *
 */
int is_member( string name, object person ) {

   if( !is_group( name ) ) {
      return 0;
   }
   
   if( member_array( person, _groups[ name ]->members ) == -1 ) {
      return 0;
   }

   return 1;

} /* is_member() */


/**
 * This function allows you to check whether an invitation for a
 * person is pending in a specific group.
 *
 * @param name the name of the group
 * @param person the person to be checked
 *
 * @return 1 if such an invitation exists, 0 if not
 */
int is_invited( string name, object person )
{
   if( member_array( person, _groups[ name ]->invited ) != -1 )
   {
      return 1;
   }
   
   return 0;
} /* is_invited() */


/**
 * This function lists all invitations to a particular group.
 * @param name the name of the group
 * @return objects who have been invited to the group
 */
object *invitations_to( string name )
{
   if( !is_group( name ) )
   {
      return 0;
   }

   return _groups[ name ]->invited;
} /* invitations_to() */


/**
 * This returns the short description of a group.
 * @param name the name of the group
 * @return the short of the group
 */
string query_group_short( string name )
{
   if( !is_group( name ) )
   {
      return 0;
   }
   return _groups[ name ]->short;
} /* query_group_short() */


/**
 * This function returns all the members of a group.
 * @param name the name of the group
 * @return an object * of the members
 */
object *members_of( string name )
{
   
   if( !is_group( name ) )
   {
      return 0;
   }
   
   return _groups[ name ]->members;
} /* members_of() */


/**
 * This function returns the person who is currently
 * leading the group, if any.
 *
 * @param name the name of the group
 * @return the object pointing to the leader, 0 if none
 */
object leader_of( string name )
{
   if( !is_group( name ) )
   {
      return 0;
   }
   return _groups[ name ]->leader;
} /* leader_of() */


/**
 * This function returns the time when the group was created.
 * @param name the name of the group
 * @return the time when the group was created
 */
int query_start_time( string name )
{
   if( !is_group( name ) )
   {
      return 0;
   }
   return _groups[ name ]->start_time;
} /* query_start_time() */


/**
 * This function concatenates the short of a group to a valid name
 * that can then be used with create_group().  The function will
 * also return invalid if a channel with such a name exists in the
 * broadcaster.
 *
 * @param short the short of the group
 * @return the name of the group, or "" if invalid
 */
string short_to_name( string short )
{
   
   string *words;
   
   short = lower_case( short );   
   words = explode( short, " " );
   words -= INVALID_WORDS;
   
   if( !sizeof( words ) ) {
      return "";
   }

   short = implode( words, " " );

   if( "/handlers/broadcaster"->query_channel_members( "group_" + short ) ) {
      return "";
   }   
   
   return short;

} /* short_to_name() */

   
/**
 * This function allows you to create a new group.  Note that the
 * name of the group shouldn't be just any name.  It should first
 * be filtered through short_to_name().  The short, set with
 * set_group_short() can be set to anything.  The "name" of a
 * group is mostly used internally to query and set stuff in
 * the handler.  The short is visible to players.
 *
 * @param name the name of the new group
 * @param founder points to the thing creating the group
 *
 * @see short_to_name()
 * @see set_group_short()
 *
 * @return 1 on success, 0 if the group already exists
 */
int create_group( string name )
{
   if( is_group( name ) ) {
      return 0;
   }
   
   _groups += ([ name : new( class group ) ]);
   
   _groups[ name ]->members = ({ });
   _groups[ name ]->invited = ({ });
   _groups[ name ]->start_time = time();

   // For stats.
   _groups_formed++;
   
   return 1;
   
} /* create_group() */


/**
 * This sets the short description of a group.
 *
 * @param name the name of the group
 * @param short_desc the short of the group
 *
 * @return 1 on success, 0 if group doesn't exist
 */
int set_group_short( string name, string short_desc )
{
   if( !is_group( name ) ) {
      return 0;
   }
   _groups[ name ]->short = short_desc;
   return 1;
} /* set_group_short() */


/**
 * This function removes a group from the handler and is only used
 * internally.  No notification of this is given to players, and
 * no cleanup is done on their part.
 * If you want to force the deletion of a group, use disband_group()
 * instead.
 *
 * @param name name of the group to be disbanded
 * @return 1 on success, 0 if group doesn't exist
 * @see disband_group()
 */
int remove_group( string name )
{
   if( !is_group( name ) ) {
      return 0;
   }
   
   if( _groups[ name ]->leader_name ) {
      LOGIN_HANDLER->remove_login_call(
         _groups[ name ]->leader_name, "leader_goes_linkdead",
         this_object() );
   }
   
   map_delete( _groups, name );
   
   return 1;
} /* remove_group() */


/**
 * This function allows you to add a person to the invite array
 * of a group.  Only invited people are allowed to join a group.
 * If the call succeeds, an internal call_out is started which
 * runs out after INVITE_TIMEOUT seconds and removes the person
 * from the array.
 *
 * @param name the name of the group
 * @param person the person we're inviting
 * @param flag set to 1 if you don't want the auto-removal of the invite
 *
 * @return 1 on success, 0 if the group doesn't exist or the person is
 *    already invited
 *
 * @see /include/group_handler.h
 */
int add_invite( string name, object person, int flag )
{
   
   if( !is_group( name ) )
   {
      return 0;
   }
   
   if( is_member( name, person ) ) 
   {
      return 0;
   }
   
   if( member_array( person, _groups[ name ]->invited ) != -1 ) 
   {
      return 0;
   }
   
   _groups[ name ]->invited += ({ person });
      
   if( !flag ) 
   {
      call_out( (: remove_invite, name, person :), INVITE_TIMEOUT );
   }
   
   return 1;
} /* add_invite() */


/**
 * This function allows you to remove an invite of a person from
 * a group.
 *
 * @param name the name of the group
 * @param person the person you want to remove
 *
 * @return 1 on success, 0 if group doesn't exist or person hasn't been
 *    invited
 */
int remove_invite( string name, object person ) 
{

   if( !is_group( name ) ) 
   {
      return 0;
   }
   
   if( !is_invited( name, person ) ) 
   {
      return 0;
   }
   
   _groups[ name ]->invited -= ({ person, 0 });
   
   return 1;
   
} /* remove_invite() */


/**
 * This function allows you to add a member to a group.
 *
 * @param name the name of the group
 * @param person the person you're adding
 * @return 1 on success, 0 if the group doesn't exist or person is
 *    already a member
 */
int add_member( string name, object person ) 
{
   if( !is_group( name ) ) 
   {
      return 0;
   }
   
   if( is_member( name, person ) ) 
   {
      return 0;
   }
    
   _groups[ name ]->members += ({ person });
   _groups[ name ]->invited -= ({ person });
   
   // Add the group effect.
   
   person->add_effect( EFFECT, name );
   
   // Add him to his group's channel.
   
   "/handlers/broadcaster"->add_object_to_channel( "group_" + name, person );
   
   notify_group( name, person, ({ "You have joined the group.",
      person->query_cap_name() + " has joined the group." }) );

   if( sizeof( _groups[ name ]->members ) > 1 )
   {
      handle_group_follow( name, person, ({ _groups[ name ]->leader }), 0, 0 );
   }

   person->set_title( GROUP_TITLE, "a member of " +
      query_group_short( name ) );
      
   return 1;
   
} /* add_member() */


/**
 * This function allows you to remove a person from a group.
 *
 * @param name the name of the group
 * @param person the person you want removed
 *
 * @return 1 on success, 0 if group doesn't exist or person isn't a
 *    member
 */
int remove_member( string name, object person )
{

   object member;

   if( !is_group( name ) )
   {
      return 0;
   }
   
   if( !is_member( name, person ) )
   {
      // Not a member.
      return 0;
   }
   
   if( person )
   {

      notify_group( name, person, ({
         "You have left the group.",
      person->query_cap_name() + " has left the group." }) );   
      
      foreach( member in person->query_assisting() )
      {

         // Stop assisting members.
         
         if( !member )
         {
            continue;
         }
         
         member->remove_assister( person );
      }

      foreach( member in person->query_assisters() )
      {
      
         // Stop being assisted.
         
         if( !member )
         {
            continue;
         }
         
         member->remove_assisting( person );
      }
      
   }

   _groups[ name ]->members -= ({ person });

   // Stop listening to the group's channel.
   
   "/handlers/broadcaster"->remove_object_from_channel( "group_" + name, person );
   
   if( person )
   {
      // Delete the effect.
      person->group_membership_removed();
   }

   if( person == leader_of( name ) && sizeof( members_of( name ) ) )
   {

      // If the person was the leader, assign a new one.
      
      notify_group( name, this_object(), "The current leader has left "
         "the group.  A new leader will be chosen randomly." );

      if( !shuffle_new_leader( name, 0 ) )
      {
         // Couldn't find a suitable new leader (which really shouldn't
         // happen unless everyone is net dead, afaik).
         
         notify_group( name, this_object(), "The choosing of a new "
            "leader has failed (oh dear).  The group is hereby "
            "disbanded.\n" );

         call_out( "disband_group", 0, name );

         return 1;
      }
   }

   // Stop following everyone.
   
   handle_group_follow( name, person, _groups[ name ]->members, 1, 1 );

   foreach( member in _groups[ name ]->members ) {

      // Make sure people stop following you.

      handle_group_follow( name, member, ({ person }), 1, 1 );
   }

   // Remove the "whois" title.
   
   person->remove_title( GROUP_TITLE );
      
   // If the group's empty, remove it altogether.
   
   if( !sizeof( members_of( name ) ) ) {
      remove_group( name );
   }

   return 1;

} /* remove_member() */


/**
 * This function allows you to set a new leader for the group.
 *
 * @param name the name of the group
 * @param person the new leader
 * @param appointer if this != 0, group is told he set the new leader
 *
 * @return 1 on success, 0 if the group doesn't exist
 */
varargs int set_leader( string name, object person, object appointer )
{

   object *followers, old_leader, member;
   
   if( !is_group( name ) )
   {
      return 0;
   }
   
   if( !is_member( name, person ) )
   {
      return 0;
   }
   
   if( !person )
   {
      return 0;
   }
   
   if( _groups[ name ]->leader_name )
   {
      LOGIN_HANDLER->remove_dynamic_login_call(
         _groups[ name ]->leader_name, "leader_goes_linkdead",
         base_name( this_object() ) );
   }

   old_leader = _groups[ name ]->leader;
   
   if( old_leader )
   {
      
      // Those who were following the old leader will now follow the new
      // leader.
      
      followers = ( old_leader->query_followers() & _groups[ name ]->members );
      followers -= ({ 0 });
      followers += ({ old_leader });
      
      foreach( member in followers )
      {
         handle_group_follow( name, member, ({ old_leader }), 1, 1 );
         handle_group_follow( name, member, ({ person }), 0, 1 );
      }
      
      // Set the title back to "only" a member.
      
      old_leader->set_title( GROUP_TITLE, "a member of " +
         query_group_short( name ) );
   }
   
   _groups[ name ]->leader = person;

   if( userp( person ) ) {
      _groups[ name ]->leader_name = person->query_name();
      LOGIN_HANDLER->add_dynamic_login_call( person->query_name(),
         "leader_goes_linkdead", base_name( this_object() ) );
   }
   else
   {
      // How can a non-user become the leader?  Beats me, but I'm paranoid.
      _groups[ name ]->leader_name = 0;
   }
   
      
   if( !appointer )
   {
      notify_group( name, person, ({ "You are now the leader of "
         "the group.", person->query_cap_name() + " is now the leader "
         "of the group." }) );
   }
   else
   {
      notify_group( name, appointer, "By the power vested in " +
         appointer->query_cap_name() + ", " + person->query_cap_name() +
         " has been appointed as the new leader of the group." );
   }

   person->set_title( GROUP_TITLE, "the leader of " +
      GROUP->query_group_short( name ) );

   return 1;
} /* set_leader() */


/**
 * This function broadcasts a message to the group's channel using
 * the broadcaster handler.  The first argument specifies the
 * group's name (not short), which also acts as the channel
 * name.  The second argument is the object doing the broadcasting.
 * The third argument varies.  It can either be a simple string,
 * in which case that string is printed as the message.  It can
 * also be a two-element string array.  The first element is
 * printed only to the object specified in the second argument.
 * The second element is printed to everyone else.
 *
 * @param name the name of the group
 * @param object person or object doing the broadcasting
 * @param message the message to be broadcasted
 *
 */
void notify_group( string name, object broadcaster, mixed message ) {
   "/handlers/broadcaster"->broadcast_to_channel( broadcaster, "group_" + name,
      ({ message, time() }) );
} /* notify_group() */


/**
 * This function does a clean removal of a group from the handler.
 * If a message is specified, it is broadcasted to the group
 * before all members are removed.
 *
 * @param name the name of the group to be disbanded
 * @param message message to be broadcasted, if any
 * @see remove_group()
 */
varargs void disband_group( string name, mixed message ) {

   object bugger, leader, *members;

   if( !is_group( name ) ) {
      return;
   }
   
   if( message ) {   
      notify_group( name, this_object(), message );
   }
   
   members = members_of( name );
   leader = leader_of( name );
   
   if( leader ) {
      members -= ({ leader });
   }
   
   foreach( bugger in members ) {
      remove_member( name, bugger );
   }
   
   remove_member( name, leader );
   
   remove_group( name );
   
} /* disband_group() */


/**
 * This function allows you to choose a new leader for the group
 * in a variety of ways.  It only includes players who are
 * interactive().
 *
 * @param group the name of the group
 * @param way 0 for random (no other ways present atm)
 * @param exclude an object array of members to exclude from the start
 * @return an object pointing to the new leader, or 0
 */
varargs object shuffle_new_leader( string group, int way, object *exclude ) {
      
   object leader;
   object *members;
   
   if( !is_group( group ) ) {
      return 0;
   }
   
   members = members_of( group );
   
   if( !sizeof( members ) ) {
      return 0;
   }
   
   if( exclude ) {
      members -= exclude;
   }
   
   members = filter( members, (: interactive( $1 ) :) );

   if( !sizeof( members ) ) {
      return 0;
   }

   switch( way ) {
      case 0:
         leader = members[ random( sizeof( members ) ) ];
         if( set_leader( group, leader ) ) {
            return leader;
         }
         return 0;
      default:
         return 0;
   }
   
} /* shuffle_new_leader() */


// Caution: The following function contains undocumented, convoluted code.
//          Browse at your own risk.

/** @ignore yes */
void leader_goes_linkdead( string player, string event_type ) {

   string group;
   object player_ob, *members;

   // This function only takes care of leader reassignment when
   // the current leader goes netdead.  Quitting or otherwise
   // destructing is taken care of in the shadow.  Don't ask me
   // why, I'm just lazy.

   if( event_type != NETDEATH && event_type != RECONNECT ) {
      LOGIN_HANDLER->remove_dynamic_login_call( player,
         "leader_goes_linkdead", base_name( this_object() ) );
      return;
   }
   
   if( !player_ob = find_player( player ) ) {
      LOGIN_HANDLER->remove_dynamic_login_call( player,
         "leader_goes_linkdead", base_name( this_object() ) );
      return;
   }
   
   group = player_ob->query_group();
   
   if( !group ) {
      LOGIN_HANDLER->remove_dynamic_login_call( player,
         "leader_goes_linkdead", base_name( this_object() ) );
      return;
   }
      
   if( _groups[ group ]->leader_name != player ) {
      // Well that was weird.  A login call should only be added for
      // the current leader.
      LOGIN_HANDLER->remove_dynamic_login_call( player,
         "leader_goes_linkdead", base_name( this_object() ) );
      return;
   }
   
   members = members_of( group );
   members -= ({ player_ob });
   
   if( !sizeof( members ) ) {
      // Looks like a one-man group.  Let's wait until he wakes up.
      return;
   }

   LOGIN_HANDLER->remove_dynamic_login_call( player,
      "leader_goes_linkdead", base_name( this_object() ) );

   notify_group( group, this_object(), "The current leader "
      "has gone netdead.  A new leader will be selected at random." );
   
   if( !shuffle_new_leader( group, 0, 0 ) ) {
      notify_group( group, this_object(), "No eligible leaders "
         "found.  The group is disbanded." );
      disband_group( group );
   }
   
} /* leader_goes_linkdead() */


/** @ignore */
void handle_group_follow( string group, object who, object *what,
   int unfollow, int silent ) {

   string short, mess_to_me, mess_to_others;
      
   switch( unfollow ) {
      case 0:
         what = filter( what, (: $1->add_follower( $( who ) ) :) );
         if( !sizeof( what ) ) {
            mess_to_me = "You begin following noone.";
            break;
         }
         short = query_multiple_short( what );
         mess_to_me = "You begin following " + short + ".";
         mess_to_others = who->query_cap_name() + " begins following " +
            short + ".";
         break;

      case 1:
         what = filter( what, (: $1->remove_follower( $( who ) ) :) );
         if( !sizeof( what ) ) {
            mess_to_me = "You stop following noone.";
            mess_to_others = 0;
            break;
         }
         short = query_multiple_short( what );
         mess_to_me = "You stop following " + short + ".";
         mess_to_others = who->query_cap_name() + " stops following " +
            short + ".";
         break;
      default:
         printf( "Barf.\n" );
         return;
   }

   if( !silent ) {
      notify_group( group, who, ({ mess_to_me, mess_to_others }) );
   }
   
} /* handle_group_follow() */


/**
 * With this function you can broadcaster a message to all or
 * some of the groups currently active.  It will not include
 * your name, so make sure you identify yourself if necessary.
 *
 * If "name" is empty or 0, the message will be broadcasted
 * to all groups.
 *
 * @param name a string array with names of groups
 * @message the message to be broadcasted
 */
void broadcast_to_groups( string *name, string message ) {
   
   string group;
   string *groups;
   
   if( name && sizeof( name ) ) {
      groups = name;
   }
   else {
      groups = keys( _groups );
   }
   
   foreach( group in groups ) {
      notify_group( group, this_player(), message );
   }

} /* broadcast_to_groups() */