dw_fluffos_v2/
dw_fluffos_v2/fluffos-2.9-ds2.05/
dw_fluffos_v2/fluffos-2.9-ds2.05/ChangeLog.old/
dw_fluffos_v2/fluffos-2.9-ds2.05/Win32/
dw_fluffos_v2/fluffos-2.9-ds2.05/compat/
dw_fluffos_v2/fluffos-2.9-ds2.05/compat/simuls/
dw_fluffos_v2/fluffos-2.9-ds2.05/include/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/clone/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/command/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/data/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/etc/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/include/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/inherit/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/inherit/master/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/log/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/compiler/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/efuns/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/operators/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/u/
dw_fluffos_v2/fluffos-2.9-ds2.05/tmp/
dw_fluffos_v2/fluffos-2.9-ds2.05/windows/
dw_fluffos_v2/lib/
dw_fluffos_v2/lib/binaries/cmds/
dw_fluffos_v2/lib/binaries/cmds/creator/
dw_fluffos_v2/lib/binaries/cmds/living/
dw_fluffos_v2/lib/binaries/cmds/player/
dw_fluffos_v2/lib/binaries/d/admin/obj/
dw_fluffos_v2/lib/binaries/d/liaison/
dw_fluffos_v2/lib/binaries/global/virtual/
dw_fluffos_v2/lib/binaries/global/virtual/setup_compiler/
dw_fluffos_v2/lib/binaries/obj/handlers/autodoc/
dw_fluffos_v2/lib/binaries/obj/handlers/terrain_things/
dw_fluffos_v2/lib/binaries/obj/misc/
dw_fluffos_v2/lib/binaries/obj/misc/buckets/
dw_fluffos_v2/lib/binaries/obj/monster/
dw_fluffos_v2/lib/binaries/obj/reactions/
dw_fluffos_v2/lib/binaries/obj/reagents/
dw_fluffos_v2/lib/binaries/secure/cmds/creator/
dw_fluffos_v2/lib/binaries/secure/master/
dw_fluffos_v2/lib/binaries/std/
dw_fluffos_v2/lib/binaries/std/dom/
dw_fluffos_v2/lib/binaries/std/effects/object/
dw_fluffos_v2/lib/binaries/std/guilds/
dw_fluffos_v2/lib/binaries/std/languages/
dw_fluffos_v2/lib/binaries/std/races/
dw_fluffos_v2/lib/binaries/std/room/
dw_fluffos_v2/lib/binaries/std/room/basic/
dw_fluffos_v2/lib/binaries/std/shops/
dw_fluffos_v2/lib/binaries/std/shops/inherit/
dw_fluffos_v2/lib/binaries/www/
dw_fluffos_v2/lib/cmds/guild-race/
dw_fluffos_v2/lib/cmds/guild-race/crafts/
dw_fluffos_v2/lib/cmds/guild-race/other/
dw_fluffos_v2/lib/cmds/playtester/
dw_fluffos_v2/lib/cmds/playtester/senior/
dw_fluffos_v2/lib/d/admin/
dw_fluffos_v2/lib/d/admin/log/
dw_fluffos_v2/lib/d/admin/mapper/31-10-01/mapmaker/event/
dw_fluffos_v2/lib/d/admin/meetings/
dw_fluffos_v2/lib/d/admin/obj/
dw_fluffos_v2/lib/d/admin/room/we_care/
dw_fluffos_v2/lib/d/admin/save/
dw_fluffos_v2/lib/d/dist/
dw_fluffos_v2/lib/d/dist/mtf/
dw_fluffos_v2/lib/d/dist/pumpkin/
dw_fluffos_v2/lib/d/dist/pumpkin/chars/
dw_fluffos_v2/lib/d/dist/pumpkin/desert/
dw_fluffos_v2/lib/d/dist/pumpkin/gumboot/
dw_fluffos_v2/lib/d/dist/pumpkin/hospital/
dw_fluffos_v2/lib/d/dist/pumpkin/inherit/
dw_fluffos_v2/lib/d/dist/pumpkin/map/
dw_fluffos_v2/lib/d/dist/pumpkin/plain/
dw_fluffos_v2/lib/d/dist/pumpkin/pumpkin/
dw_fluffos_v2/lib/d/dist/pumpkin/save/
dw_fluffos_v2/lib/d/dist/pumpkin/squash/
dw_fluffos_v2/lib/d/dist/pumpkin/terrain/
dw_fluffos_v2/lib/d/dist/pumpkin/woods/
dw_fluffos_v2/lib/d/dist/start/
dw_fluffos_v2/lib/d/learning/TinyTown/buildings/
dw_fluffos_v2/lib/d/learning/TinyTown/map/
dw_fluffos_v2/lib/d/learning/TinyTown/roads/
dw_fluffos_v2/lib/d/learning/add_command/
dw_fluffos_v2/lib/d/learning/arms_and_weps/
dw_fluffos_v2/lib/d/learning/chars/
dw_fluffos_v2/lib/d/learning/cutnpaste/
dw_fluffos_v2/lib/d/learning/examples/npcs/
dw_fluffos_v2/lib/d/learning/examples/player_houses/npcs/
dw_fluffos_v2/lib/d/learning/examples/terrain_map/basic/
dw_fluffos_v2/lib/d/learning/functions/
dw_fluffos_v2/lib/d/learning/handlers/
dw_fluffos_v2/lib/d/learning/help_topics/npcs/
dw_fluffos_v2/lib/d/learning/help_topics/objects/
dw_fluffos_v2/lib/d/learning/help_topics/rcs_demo/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/crowd/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/situations/
dw_fluffos_v2/lib/d/learning/items/
dw_fluffos_v2/lib/d/learning/save/
dw_fluffos_v2/lib/d/liaison/
dw_fluffos_v2/lib/d/liaison/NEWBIE/doc/
dw_fluffos_v2/lib/d/liaison/NEWBIE/save/oldlog/
dw_fluffos_v2/lib/db/
dw_fluffos_v2/lib/doc/
dw_fluffos_v2/lib/doc/creator/
dw_fluffos_v2/lib/doc/creator/autodoc/include/reaction/
dw_fluffos_v2/lib/doc/creator/autodoc/include/ritual_system/
dw_fluffos_v2/lib/doc/creator/autodoc/include/talker/
dw_fluffos_v2/lib/doc/creator/autodoc/include/terrain_map/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/baggage/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/clock/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/clothing/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/cont_save/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/corpse/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/money/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/monster/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/scabbard/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/service_provider/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/state_changer/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/wand/
dw_fluffos_v2/lib/doc/creator/autodoc/std/book_dir/
dw_fluffos_v2/lib/doc/creator/autodoc/std/key/
dw_fluffos_v2/lib/doc/creator/autodoc/std/learning/
dw_fluffos_v2/lib/doc/creator/autodoc/std/map/
dw_fluffos_v2/lib/doc/creator/autodoc/std/race/
dw_fluffos_v2/lib/doc/creator/autodoc/std/weapon_logic/
dw_fluffos_v2/lib/doc/creator/files/
dw_fluffos_v2/lib/doc/creator/policy/
dw_fluffos_v2/lib/doc/creator/room/
dw_fluffos_v2/lib/doc/effects/
dw_fluffos_v2/lib/doc/ideas/
dw_fluffos_v2/lib/doc/known_command/
dw_fluffos_v2/lib/doc/lpc/basic_manual/
dw_fluffos_v2/lib/doc/lpc/intermediate/
dw_fluffos_v2/lib/doc/new/add_command/
dw_fluffos_v2/lib/doc/new/handlers/
dw_fluffos_v2/lib/doc/new/living/
dw_fluffos_v2/lib/doc/new/living/race/
dw_fluffos_v2/lib/doc/new/living/spells/
dw_fluffos_v2/lib/doc/new/player/
dw_fluffos_v2/lib/doc/new/room/guild/
dw_fluffos_v2/lib/doc/new/room/outside/
dw_fluffos_v2/lib/doc/new/room/storeroom/
dw_fluffos_v2/lib/doc/object/
dw_fluffos_v2/lib/doc/playtesters/
dw_fluffos_v2/lib/doc/policy/
dw_fluffos_v2/lib/doc/weapons/
dw_fluffos_v2/lib/global/handlers/
dw_fluffos_v2/lib/global/virtual/setup_compiler/
dw_fluffos_v2/lib/include/
dw_fluffos_v2/lib/include/cmds/
dw_fluffos_v2/lib/include/effects/
dw_fluffos_v2/lib/include/npc/
dw_fluffos_v2/lib/include/shops/
dw_fluffos_v2/lib/net/daemon/chars/
dw_fluffos_v2/lib/net/inherit/
dw_fluffos_v2/lib/net/intermud3/
dw_fluffos_v2/lib/net/intermud3/services/
dw_fluffos_v2/lib/net/obj/
dw_fluffos_v2/lib/net/save/
dw_fluffos_v2/lib/net/smnmp/
dw_fluffos_v2/lib/net/snmp/
dw_fluffos_v2/lib/obj/amulets/
dw_fluffos_v2/lib/obj/b_day/
dw_fluffos_v2/lib/obj/examples/
dw_fluffos_v2/lib/obj/food/alcohol/
dw_fluffos_v2/lib/obj/food/chocolates/
dw_fluffos_v2/lib/obj/food/fruits/
dw_fluffos_v2/lib/obj/food/meat/
dw_fluffos_v2/lib/obj/food/nuts/
dw_fluffos_v2/lib/obj/food/seafood/
dw_fluffos_v2/lib/obj/food/vegetables/
dw_fluffos_v2/lib/obj/fungi/
dw_fluffos_v2/lib/obj/furnitures/artwork/
dw_fluffos_v2/lib/obj/furnitures/bathroom/
dw_fluffos_v2/lib/obj/furnitures/beds/
dw_fluffos_v2/lib/obj/furnitures/cabinets/
dw_fluffos_v2/lib/obj/furnitures/chairs/
dw_fluffos_v2/lib/obj/furnitures/chests/
dw_fluffos_v2/lib/obj/furnitures/clocks/
dw_fluffos_v2/lib/obj/furnitures/crockery/
dw_fluffos_v2/lib/obj/furnitures/cupboards/
dw_fluffos_v2/lib/obj/furnitures/cushions/
dw_fluffos_v2/lib/obj/furnitures/fake_plants/
dw_fluffos_v2/lib/obj/furnitures/lamps/
dw_fluffos_v2/lib/obj/furnitures/mirrors/
dw_fluffos_v2/lib/obj/furnitures/outdoor/
dw_fluffos_v2/lib/obj/furnitures/safes/
dw_fluffos_v2/lib/obj/furnitures/shelves/
dw_fluffos_v2/lib/obj/furnitures/sideboards/
dw_fluffos_v2/lib/obj/furnitures/sofas/
dw_fluffos_v2/lib/obj/furnitures/stoves/
dw_fluffos_v2/lib/obj/furnitures/tables/
dw_fluffos_v2/lib/obj/furnitures/wardrobes/
dw_fluffos_v2/lib/obj/handlers/
dw_fluffos_v2/lib/obj/handlers/autodoc/
dw_fluffos_v2/lib/obj/jewellery/anklets/
dw_fluffos_v2/lib/obj/jewellery/bracelets/
dw_fluffos_v2/lib/obj/jewellery/earrings/
dw_fluffos_v2/lib/obj/jewellery/misc/
dw_fluffos_v2/lib/obj/jewellery/necklaces/
dw_fluffos_v2/lib/obj/jewellery/rings/
dw_fluffos_v2/lib/obj/media/
dw_fluffos_v2/lib/obj/misc/buckets/
dw_fluffos_v2/lib/obj/misc/jars/
dw_fluffos_v2/lib/obj/misc/papers/
dw_fluffos_v2/lib/obj/misc/player_shop/
dw_fluffos_v2/lib/obj/misc/shops/
dw_fluffos_v2/lib/obj/misc/traps/
dw_fluffos_v2/lib/obj/monster/
dw_fluffos_v2/lib/obj/monster/godmother/
dw_fluffos_v2/lib/obj/monster/transport/
dw_fluffos_v2/lib/obj/plants/inherit/
dw_fluffos_v2/lib/obj/potions/
dw_fluffos_v2/lib/open/boards/
dw_fluffos_v2/lib/save/autodoc/
dw_fluffos_v2/lib/save/bank_accounts/
dw_fluffos_v2/lib/save/boards/frog/
dw_fluffos_v2/lib/save/books/bed_catalog/
dw_fluffos_v2/lib/save/creators/
dw_fluffos_v2/lib/save/mail/
dw_fluffos_v2/lib/save/mail/p/
dw_fluffos_v2/lib/save/soul/data/
dw_fluffos_v2/lib/save/tasks/
dw_fluffos_v2/lib/save/vaults/
dw_fluffos_v2/lib/secure/cmds/lord/
dw_fluffos_v2/lib/secure/config/
dw_fluffos_v2/lib/secure/items/
dw_fluffos_v2/lib/secure/player/
dw_fluffos_v2/lib/soul/
dw_fluffos_v2/lib/soul/i/
dw_fluffos_v2/lib/soul/j/
dw_fluffos_v2/lib/soul/k/
dw_fluffos_v2/lib/soul/o/
dw_fluffos_v2/lib/soul/q/
dw_fluffos_v2/lib/soul/to_approve/
dw_fluffos_v2/lib/soul/u/
dw_fluffos_v2/lib/soul/v/
dw_fluffos_v2/lib/soul/wish_list/
dw_fluffos_v2/lib/soul/y/
dw_fluffos_v2/lib/soul/z/
dw_fluffos_v2/lib/std/creator/
dw_fluffos_v2/lib/std/effects/
dw_fluffos_v2/lib/std/effects/attached/
dw_fluffos_v2/lib/std/effects/external/
dw_fluffos_v2/lib/std/effects/fighting/
dw_fluffos_v2/lib/std/effects/other/
dw_fluffos_v2/lib/std/environ/
dw_fluffos_v2/lib/std/guilds/
dw_fluffos_v2/lib/std/hospital/
dw_fluffos_v2/lib/std/house/
dw_fluffos_v2/lib/std/house/onebedhouse/
dw_fluffos_v2/lib/std/house/onebedhut/
dw_fluffos_v2/lib/std/house/tworoomflat/
dw_fluffos_v2/lib/std/languages/
dw_fluffos_v2/lib/std/liquids/
dw_fluffos_v2/lib/std/nationality/
dw_fluffos_v2/lib/std/nationality/accents/
dw_fluffos_v2/lib/std/nationality/accents/national/
dw_fluffos_v2/lib/std/nationality/accents/regional/
dw_fluffos_v2/lib/std/npc/goals/
dw_fluffos_v2/lib/std/npc/goals/basic/
dw_fluffos_v2/lib/std/npc/goals/misc/
dw_fluffos_v2/lib/std/npc/inherit/
dw_fluffos_v2/lib/std/npc/plans/
dw_fluffos_v2/lib/std/npc/plans/basic/
dw_fluffos_v2/lib/std/outsides/
dw_fluffos_v2/lib/std/races/shadows/
dw_fluffos_v2/lib/std/room/basic/topography/
dw_fluffos_v2/lib/std/room/controller/
dw_fluffos_v2/lib/std/room/controller/topography/
dw_fluffos_v2/lib/std/room/furniture/games/
dw_fluffos_v2/lib/std/room/furniture/inherit/
dw_fluffos_v2/lib/std/room/inherit/carriage/
dw_fluffos_v2/lib/std/room/inherit/topography/
dw_fluffos_v2/lib/std/room/punishments/
dw_fluffos_v2/lib/std/room/topography/area/
dw_fluffos_v2/lib/std/room/topography/iroom/
dw_fluffos_v2/lib/std/room/topography/milestone/
dw_fluffos_v2/lib/std/shadows/
dw_fluffos_v2/lib/std/shadows/attached/
dw_fluffos_v2/lib/std/shadows/curses/
dw_fluffos_v2/lib/std/shadows/disease/
dw_fluffos_v2/lib/std/shadows/fighting/
dw_fluffos_v2/lib/std/shadows/room/
dw_fluffos_v2/lib/std/shops/controllers/
dw_fluffos_v2/lib/std/shops/objs/
dw_fluffos_v2/lib/std/shops/player_shop/
dw_fluffos_v2/lib/std/shops/player_shop/office_code/
dw_fluffos_v2/lib/std/socket/
dw_fluffos_v2/lib/www/
dw_fluffos_v2/lib/www/external/autodoc/
dw_fluffos_v2/lib/www/external/java/telnet/Documentation/
dw_fluffos_v2/lib/www/external/java/telnet/Documentation/images/
dw_fluffos_v2/lib/www/external/java/telnet/examples/
dw_fluffos_v2/lib/www/external/java/telnet/tools/
dw_fluffos_v2/lib/www/pics/
dw_fluffos_v2/lib/www/secure/creator/
dw_fluffos_v2/lib/www/secure/editors/
dw_fluffos_v2/lib/www/secure/survey_results/
dw_fluffos_v2/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();
int set_group_short( string, string);

/** @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( 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 method renames the group.
 * @param group the old group name
 * @param new_group the new group name
 */
int rename_group(string group, string new_group) {
   object member;

   if (is_group(new_group) || new_group == group) {
      return 0;
   }

   _groups[new_group] = _groups[group];
   map_delete(_groups, group);
   set_group_short(new_group, new_group);
   foreach (member in _groups[new_group]->members) {
      if (!member) {
         continue;
      }
      // Remove the old group stuff.
      BROADCASTER->remove_object_from_channel( "group_" + group, member );

      member->group_membership_removed();

      member->add_effect( EFFECT, new_group);

      // Add him to his group's channel.

      BROADCASTER->add_object_to_channel( "group_" + new_group, member );

      member->set_title( GROUP_TITLE, "a member of " +
      query_group_short( new_group ) );

   }
   notify_group( new_group, this_object(), ({ "", "The group has been renamed to " +
      new_group + "." }) );
   return 1;
}


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

      if (sizeof(members_of(name)) == 1) {
         notify_group( name, this_object(), "The current leader has left "
            "the group, you are now all alone.  Better start recruiting.");
      } else {
         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.
   if (person && objectp(person)) { 
      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 ) {
   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, (: $1 && 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() */


mapping query_dynamic_auto_load() {
   return ([ "groups" : _groups,
             "groups formed" : _groups_formed ]);
}

void init_dynamic_arg(mapping map) {
   _groups = map["groups"];
   _groups_formed = map["groups formed"];
}