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/
// Gototh, 10/07/99
/**
 * This inherit handles all the placement code and description stuff for
 * player housing.
 * @author Gototh
 * @started 10/07/99
 */

/*
 * Todo:
 *
 *  - Convert from properties to functions.
 *  - Document fully.
 */

#include <obj_parser.h>
#include <dirs.h>
#include <room/placement.h>

#define PLAYTESTING
//#define DEBUG

#define MAX_PLACED 10

/*
#define PLACEMENT_CURRENT_ROOM_VERB "current room verb"
#define CURRENT_ROOM_OTHER "current room other"
#define CURRENT_ROOM_POSITION "current room position"
#define CURRENT_ROOM_RELATIVE "current room relative"
#define IMMOVABLE "immovable"
 */
#define PLACEMENT_CURRENT_ROOM_HIDDEN "current room hidden"

#define OPPOSITE 0
#define ABOVE    1
#define BELOW    2
#define RIGHT    3
#define LEFT     4

#define OPPOSITE_DESCS ({ \
  "opposite which", \
  "facing which", \
})

#define ABOVE_DESCS ({ \
  "above which", \
  "over which", \
})

#define BELOW_DESCS ({ \
  "under which", \
  "below which", \
})

#define RIGHT_DESCS ({ \
  "to the right of which", \
})

#define LEFT_DESCS ({ \
  "to the left of which", \
})

#define HANGING_VERBS ({ \
  "hanging", \
  "suspended", \
  "dangling", \
})

#define HANGING_PREPOSITIONS ({ \
  "from", \
  "on", \
})

#define WALL_PREPOSITIONS ({ \
  "beside", \
  "against", \
})

#define CORNER_PREPOSITIONS ({ \
  "in", \
})

#define ABOUT_PREPOSITIONS ({ \
  "near", \
  "at", \
})
//  "in front of", \

string get_pos_desc(int pos_int);
int do_place(object *obs, string, string, mixed *args, string);
int do_help(object *);
int get_pos_int(string str);
string extra_look(object);
string object_position_desc(object thing);
string object_desc(object thing);
string object_desc2(object thing);
object *build_structure();
int find_opposite(int pos, object *structure);
int find_above(int pos, object *structure);
int find_below(int pos, object *structure);
int find_left(int pos, object *structure);
int find_right(int pos, object *structure);
int get_structure_index(object ob);
varargs int is_beside_wall(int pos, int flag);
varargs int is_in_corner(int pos, int flag);

nosave object *needs_described;
nosave object *have_described;
nosave int orientation;
nosave mapping _wall_names;
nosave string furniture_long = "";

void init() {
 if(!this_player() || !interactive(this_player()))
     return;
     
  this_player()->add_command("place", this_object(),
    "<indirect:object'furniture'> so it is <word'verb'> "
    "{"+implode(ABOUT_PREPOSITIONS+HANGING_PREPOSITIONS+WALL_PREPOSITIONS+CORNER_PREPOSITIONS, "|")+"} "
    "the <string:'location'> [wall|corner]");
  
  // Without a verb.
  this_player()->add_command("place", this_object(),
    "<indirect:object'furniture'> "
    "{"+implode(ABOUT_PREPOSITIONS+HANGING_PREPOSITIONS+WALL_PREPOSITIONS+CORNER_PREPOSITIONS, "|")+"} the <string'location'> [wall|corner]");

  this_player()->add_command("displace", this_object(),
    "<indirect:object'furniture'>");

  this_player()->add_command("place", this_object(),
                             "help", (: do_help(0) :));
  this_player()->add_command("place", this_object(),
                             "help <indirect:object'furniture'>",
                             (: do_help($1[0]) :));
}

/**
 * This method sets a special description for a wall.  For example making
 * it a fence or something.
 * @param direction the direction to do a thing for
 * @param name the nice name
 */
void set_wall_name(string direction, string name) {
   int pos;

   if (!_wall_names) {
      _wall_names = ([ ]);
   }
   pos = get_pos_int(direction);
   _wall_names[pos] = name;
} /* set_wall_name() */

/**
 * This method gets the wall name from the integer position.
 * @param pos the integer position
 * @return the wall name
 */
string query_wall_name_pos(int pos) {
   if (!_wall_names) {
      _wall_names = ([ ]);
   }
   if (_wall_names[pos]) {
      return _wall_names[pos];
   }
   if(!orientation)
   {
      if(pos % 2)
      {
         return "corner";
      }
      return "wall";
   }
   if(pos % 2)
   {
      return "wall";
   }
   return "corner";
} /* query_wall_name_pos() */

/** 
 * This method returns the special description for the wall in that
 * direction.
 * @param direction
 * @return the special description, or wall if none
 */
string query_wall_name(string direction) {
   int pos;

   pos = get_pos_int(direction);
   return query_wall_name_pos(pos);
} /* query_wall_name() */

string query_another(object ob)
{
  object *obs;
  string another;

  obs = filter(have_described, 
    (: $1->query_plural() == $(ob->query_plural()) :));

  switch(sizeof(obs))
  {
    case 0 :
      another = " a";
      break;
    case 1 :
      another = " another";
      break;
    default :
      another = " yet another";
      break;
  }
  return another;
} /* query_another() */

string query_more(object ob)
{
  object *obs;
  string more;

  obs = filter(have_described, 
    (: $1->query_plural() == $(ob->query_plural()) :));

  switch(sizeof(obs))
  {
    case 0 :
      more = "";
      break;
    default :
      more = " more";
      break;
  }
  return more;
} /* query_more() */

string clutter_string(object ob) {
  mixed *things;
  string cluttered = "";
  
  if(ob->query_has_surface()) {
    things = ob->find_inv_match("all", ob);
    things = unique_array(things, (: $1->query_plural() :));

    switch(sizeof(things)) {
      case 1 :
        if(sizeof(things[0]) == 1)
          cluttered = " is";
        else
          cluttered = " are";

        cluttered += " " + query_multiple_short(things[0]); 
        break;
      default :
        break;
    }
  }
  
  return cluttered;
} /* clutter_string() */

string query_multiple_furniture_short(object *obs) {
  object *group;
  mixed *things;
  string str;
  string *strs;
  strs = ({ });
  things = unique_array(obs, (: $1->query_plural() :));
  foreach(group in things)
  {
    switch(sizeof(group))
    {
      case 0 :
        break;
      case 1 :
        str = query_another(group[0])[1..];
        str += " " + group[0]->query_short();
        break;
      default :
        str = query_num(sizeof(group));
        str += query_more(group[0]);
        str += " " + group[0]->query_plural();
        break;
    }
    strs += ({ str });
  }
  return query_multiple_short(strs);
} /* query_multiple_furniture_short() */

/*
 * This function returns true if the object has been placed in 
 * the corner.
 */
varargs int is_in_corner(int pos, int flag)
{
  if(orientation && !flag)
    return is_beside_wall(pos, 1);
  
  if(member_array(pos, ({ 3, 5, 7, 9 })) != -1)
    return 1;

  return 0;
} /* is_in_corner() */

/*
 * This function returns true if the object has been placed 
 * beside a wall.
 */
varargs int is_beside_wall(int pos, int flag)
{
  if(orientation && !flag)
  {
    return is_in_corner(pos, 1);
  }
  if(member_array(pos, ({ 2, 4, 6, 8 })) != -1)
  {
    return 1;
  }
  return 0;
} /* is_beside_wall() */

/*
 * This function returns true if the object is hanging from a
 * wall.
 */
int is_wall_hanging(int pos)
{
  if(orientation)
  {
    if(member_array(pos, ({ 13, 15, 17, 19 })) != -1)
    {
      return 1;
    }
    return 0;
  }
  if(member_array(pos, ({ 12, 14, 16, 18 })) != -1)
  {
    return 1;
  }
  return 0;
} /* is_hanging() */

int is_in_centre(int pos)
{
  if(pos == 1)
  {
    return 1;
  }
  return 0;
} /* is_hanging() */

int query_max_space_around(int pos)
{
  if(is_in_corner(pos))
  {
    return 2;
  }
  if(is_beside_wall(pos))
  {
    return 3;
  }
  if(is_in_centre(pos))
  {
    return 4;
  }
  return 0;
} /* query_max_space_around() */

int do_help(object ob) {
  string str;
  string *bits;

  if(ob && !ob->query_furniture())
    return this_player()->add_failed_mess(this_object(),
                                          "$I is not a piece of furniture.\n",
                                          ({ ob }));
  
  
  str = "The place command requires a furniture object, an optional verb, "
    "a preposition and a location.  ";
  
  if(!ob) {
    str += "Which verbs are available depends on the furniture.  Hanging "
      "furniture can use " + query_multiple_short(HANGING_VERBS) + ".  ";
  } else {
    if(ob->query_allowed_room_verbs())
      bits = filter(keys(ob->query_allowed_room_verbs()), (: $1 != "" :));

    switch(sizeof(bits)) {
    case 0:
      str += "The " + ob->the_short() + " has no verbs.\n";
      break;
    case 1:
      str += "The verb for " + ob->the_short() + " is " +
        query_multiple_short(bits) + ".\n";
      break;
    default:
      str += "The verbs available for " + ob->the_short() + " are " +
        query_multiple_short(bits) + ".\n";
    }
  }

  str += "The prepositions for hanging things are " +
    query_multiple_short(HANGING_PREPOSITIONS) + ", while items may be "
    "placed " + query_multiple_short(WALL_PREPOSITIONS) + " walls, " +
    query_multiple_short(CORNER_PREPOSITIONS) + " corners, or " +
    query_multiple_short(ABOUT_PREPOSITIONS) + " other objects.  " 
    "The " + query_multiple_short(({"ceiling", "floor", "centre", "north",
                                      "south", "east", "west", "northeast",
                                      "southeast", "northwest", "southwest"}))+
    " may be used as locations.\n";
  write(str);
  return 1;
}

int do_place(object *obs, string, string, mixed *args, string)
{
  int pos, allowed;
  string k, v;
  mapping allowed_verbs;
  object *things;
  object *placed;
  object* already_around;
  class obj_match result;

#ifdef DEBUG
  debug_printf("Obs: %O, args: %O", obs, args);
#endif
  
  if(this_player() &&
     !this_object()->test_occupier(this_player()->query_name()) &&
     !this_player()->query_creator()) {
    this_player()->add_failed_mess(this_object(),
                                   "You don't own this house.\n", ({ }));
    return 0;
  }
  if(sizeof(obs) > 1) {
    this_player()->add_failed_mess(this_object(),
      "You may only place one object at a time.\n", ({ }));
    return 0;
  }
  if(!obs[0]->query_furniture()) {
    this_player()->add_failed_mess(this_object(),
      "$I is not suitable furniture.\n", ({ obs[0] }));
    return 0;
  }

  placed = filter(all_inventory(this_object()), 
    (: $1->query_property(PLACEMENT_CURRENT_ROOM_POSITION) :));

  if(!obs[0]->query_property(PLACEMENT_CURRENT_ROOM_POSITION))
  {
    if(sizeof(placed) >= MAX_PLACED)
    {
      if(this_player())
         this_player()->add_failed_mess(this_object(),
                                        "$C$"+this_object()->the_short() +
                                        " is too cluttered to place anything "
                                        "else.\n", ({ }));
      return 0;
    }
  }

  if(obs[0]->query_property(PLACEMENT_IMMOVABLE)) {
    this_player()->add_failed_mess(this_object(),
                                   "Try as you might you cannot move $I.\n",
                                   ({ obs[0] }));
    return 0;
  }
  
  /*
   * Wrestle with the args to see if they're not using a verb.  If
   * they're not, reposition the args array so it still works.
   */
  if(member_array(args[1], HANGING_PREPOSITIONS + WALL_PREPOSITIONS
    + CORNER_PREPOSITIONS + ABOUT_PREPOSITIONS) != -1)
  {
    // allow "north wall" or "northern wall"
    args[2] = replace_string(args[2], "ern ", " ");
    
    if((member_array(EXPAND_EXIT(args[2]), ({"north", "south", "east", 
      "west", "northeast", "southeast", "southwest", "northwest",    
      "centre"})) != -1) || sizeof(match_objects_for_existence(args[2], 
      ({ this_object() }) )))
    {
      int i;
      string *new_args;
      
      new_args = ({ });
      new_args += ({ args[0] });
      new_args += ({ "" });
      for(i = 1; i < sizeof(args); i++)
      {
        new_args += ({ args[i] });
      }
      args = new_args;
    }
  }

  allowed_verbs = obs[0]->query_allowed_room_verbs();

  if(allowed_verbs)
  {
    allowed_verbs += ([ "" : "" ]);
    foreach(k, v in allowed_verbs)
    {
      if(k == args[1] || v == args[1])
      {
        allowed = 1;
        break;
      }
    }
  }
  if(!allowed)
  {
    string *verb_keys;
    string extra = "";
    
    // Tell them the verbs they're allowed to use.
    if(allowed_verbs)
    {
      verb_keys = keys(allowed_verbs);
      verb_keys -= ({ "" });
      verb_keys = map(verb_keys, (: ("\"" + $1 + "\"") :));
      extra = "  You may only place $I " + 
        query_multiple_short(verb_keys) + ".";
    }

    if(this_player())
      this_player()->add_failed_mess(this_object(),
      "You can't place $I with that verb." 
      + extra + "\n", ({ obs[0] }));
    return 0;
  }

  /*
   * Needed for compatability with two command syntaxes.
   */
  if(sizeof(args) == 3)
  {
    string *shrapnel;
    shrapnel = explode(args[2], " ");
    args -= ({ args[2] });
    args += shrapnel;
  }

  /*
   * Makes handling messages easier later on.
   */
  if(args[1] != "")
  {
    args[1] = " " + args[1];
  }

  pos = get_pos_int(args[3]);

  // Prevent "on the centre" by turning "on the floor" into "in the centre"
  if(args[3] == "floor" && args[2] == "on")
    args[2] = "in";
  
   
  if(member_array(args[2], ABOUT_PREPOSITIONS) != -1)
  {
    /*
     * Check to see if they're trying to position the object relative
     * to another object.
     */
    if(!pos)
    {
      result = (class obj_match)match_objects_in_environments(args[3], ({ this_object() }));

      if (result->result != OBJ_PARSER_SUCCESS) {
         if(this_player()) {
            add_failed_mess( match_objects_failed_mess( result ));
         }
         return 0;
      }

      things = result->objects;

      pos = things[0]->query_property(PLACEMENT_CURRENT_ROOM_POSITION);
      
      if(pos < -1)
      {
        if(this_player())
        this_player()->add_failed_mess(this_object(),
          "$I is placed around another piece of "
          "furniture.\n", ({ things[0]->the_short() }));
        return 0;
      }
      
      if(pos == get_structure_index(obs[0]))
      {
        if(this_player()) {
          if(sizeof(args) >= 4)
            this_player()->add_failed_mess(this_object(),
                   "You can't place $I " + args[2] + " the " + args[3] + ".\n",
                                           ({ obs[0] }));
          else
            this_player()->add_failed_mess(this_object(),
                   "You can't place $I " + args[2] + " itself.\n", ({ obs[0] }));
        }
        return 0;
      }
      
      if(pos > 9)
      {
        if(this_player())
        this_player()->add_failed_mess(this_object(),
          "You can't place $I " + args[2] + " " +
          things[0]->the_short() + ".\n", ({ obs[0] }));
        return 0;
      }
      
      if(pos == -1)
      {
        if(this_player())
        this_player()->add_failed_mess(this_object(),
          "$I isn't placed.\n", ({ things[0] }));
        return 0;
      }
    }
    /*
     * Okay, change the sign on pos.  This'll mark it as one of the
     * object positioned around the object in pos.
     */
    already_around = filter(all_inventory(this_object()), 
      (: $1->query_property(PLACEMENT_CURRENT_ROOM_POSITION) == -($(pos)) :));
    if(sizeof(already_around) > query_max_space_around(pos))
    {
      if(this_player())
      this_player()->add_failed_mess(this_object(),
        "There isn't enough room to place " + obs[0]->the_short()
        + " there.\n", ({ }));
      return 0;
    }
    pos = -(pos);
  }

  if(pos > 0)
  {
    object *already_there;
    if(member_array(args[1][1..], HANGING_VERBS) != -1)
    {
      /*
       * Make sure they're not trying to 'hang' it in a corner.
       */
      if(is_in_corner(pos))
      {
        if(this_player())
        this_player()->add_failed_mess(this_object(),
          "You may not place $I" + args[1] + " in a "
          "corner.\n", ({ obs[0] }));
        return 0;
      }
      pos += 10;
    }
    already_there = filter(all_inventory(this_object()),
      (: $1->query_property(PLACEMENT_CURRENT_ROOM_POSITION) == $(pos) :));
    if((sizeof(already_there)) && (already_there[0] != obs[0]))
    {
      if(this_player())
      this_player()->add_failed_mess(this_object(),
        "$I is already"
        + already_there[0]->query_property(PLACEMENT_CURRENT_ROOM_VERB) + " "
        + already_there[0]->query_property(PLACEMENT_CURRENT_ROOM_OTHER) + " "
        + get_pos_desc(pos) + ".\n", ({ already_there[0] }));
      return 0;
    }
  }
  
  if(is_wall_hanging(pos))
  {
    if(member_array(args[2], HANGING_PREPOSITIONS) == -1)
    {
      string *preps;
      string extra = "";

      preps = HANGING_PREPOSITIONS;
      preps = map(preps, (: ("\"" + $1 + "\"") :));
      
      extra = "  You may only use " + query_multiple_short(preps) + 
        " as prepositions."; 
        
#ifdef PLAYTESTING

      extra += "  Mail suggestions for more to Gototh.";
      
#endif

      if(this_player())
      this_player()->add_failed_mess(this_object(),
        "You may not place $I"+ args[1] + " \"" + args[2] + "\" "
        + get_pos_desc(pos) + "." + extra + "\n", ({ obs[0] }));
      return 0;
    }
  }
  if(is_beside_wall(pos))
  {  
    if(member_array(args[2], WALL_PREPOSITIONS) == -1)
    {
      string *preps;
      string extra = "";

      preps = WALL_PREPOSITIONS;
      preps = map(preps, (: ("\"" + $1 + "\"") :));
      
      extra = "  You may only use " + query_multiple_short(preps) + 
        " as prepositions.";
        
#ifdef PLAYTESTING

      extra += "  Mail suggestions for more to Gototh.";
      
#endif

      if(this_player())
      this_player()->add_failed_mess(this_object(),
        "You may not place $I"+ args[1] + " \"" + args[2] + "\" "
        + get_pos_desc(pos) + "." + extra + "\n", ({ obs[0] }));
      return 0;
    }
  }
  if(is_in_corner(pos))
  {
    if(member_array(args[2], CORNER_PREPOSITIONS) == -1)
    {
      string *preps;
      string extra = "";    

      preps = CORNER_PREPOSITIONS;
      preps = map(preps, (: ("\"" + $1 + "\"") :));
      
      extra = "  You may only use " + query_multiple_short(preps) + 
        " as a preposition.";

#ifdef PLAYTESTING

      extra += "  Mail suggestions for more to Gototh.";
      
#endif
      if(this_player())
      this_player()->add_failed_mess(this_object(),
        "You may not place $I"+ args[1] + " \"" + args[2] + "\" "
        + get_pos_desc(pos) + "." + extra + "\n", ({ obs[0] }));
      return 0;
    }
  }

  if(!pos)
  {
    if(this_player())
       this_player()->add_failed_mess(this_object(),
      "You may not place $I"+ args[1] + " " + args[2] + " the \""
      + args[3] + "\".\n", ({ obs[0] }));
    return 0;
  }

  obs[0]->add_property(PLACEMENT_CURRENT_ROOM_VERB, args[1]);
  obs[0]->add_property(PLACEMENT_CURRENT_ROOM_OTHER, args[2]);
  obs[0]->add_property(PLACEMENT_CURRENT_ROOM_POSITION, pos);

  if(environment(obs[0]) != this_object())
  {
    obs[0]->move(this_object());
  }
  if(pos > 0)
  {
    if(this_player())
      this_player()->add_succeeded_mess(this_object(),
      "$N $V $I so that it is" + args[1] + " " + args[2]
      + " " + get_pos_desc(pos) + ".\n", ({ obs[0] }));
  }
  else
  {
    if(sizeof(things))
    {
      if(this_player())
        this_player()->add_succeeded_mess(this_object(),
        "$N $V $I so that it is" + args[1] + " " + args[2] +
        " " + things[0]->the_short() + ".\n", ({ obs[0] }));
    }
    else
    {
      pos = -(pos);
      if(this_player())
      this_player()->add_succeeded_mess(this_object(),
        "$N $V $I so that it is" + args[1] + " " + args[2] +
        " " + get_pos_desc(pos) + ".\n", ({ obs[0] }));
    }
  }
  event(this_object(), "save", this_object());
  furniture_long = "";
  return 1;  
} /* do_place() */

int get_pos_int(string str)
{
  string dir;
  int pos_int;

  dir = explode(str, " ")[0];
  dir = EXPAND_EXIT(dir);    

  switch(dir)
  {
    case "ceiling" :
    case "floor" :
    case "centre" :
      pos_int = 1;
      break;
    case "north" :
      pos_int = 2;
      break;
    case "south" :
      pos_int = 6;
      break;
    case "east" :
      pos_int = 4;
      break;
    case "west" :
      pos_int = 8;
      break;
    case "northeast" :
      pos_int = 3;
      break;
    case "southeast" :
      pos_int = 5;
      break;
    case "southwest" :
      pos_int = 7;
      break;
    case "northwest" :
      pos_int = 9;
      break;
  }
  return pos_int;
} /* get_pos_int() */

/*
 * The following functions are all geared towards returning
 * the index of where an object sits in relation to the object
 * in index position 'pos' in the structure array.
 */
int find_opposite(int pos, object *structure)
{
  int opp;
  if(pos == 1 || pos == 11)
  {
    return 0;
  }
  if((pos > 5 && pos < 10) || (pos > 11 && pos > 15))
  {
    opp = pos - 4;
  }
  else
  {
    opp = pos + 4;
  }
  return opp;
} /* find_opposite() */

int find_above(int pos, object *structure)
{
  int above;
  if(pos < 10)
  {
    above = pos + 10;
  }
  return above;
} /* find_above() */

int find_below(int pos, object *structure)
{
  int below;
  if(pos > 10)
  {
    below = pos - 10;
  }
  return below;
} /* find_below() */

int find_left(int pos, object *structure)
{
  int left;

  if(pos == 1 || pos == 11)
  {
    return 0;
  }
  left = pos - 1;
  if(left == 1)
  {
    left = 9;
  }
  if(left == 11)
  {
    left = 19;
  }
  return left;
} /* find_left() */

int find_right(int pos, object *structure)
{
  int right;

  if(pos == 1 || pos == 11)
  {
    return 0;
  }
  right = pos + 1;
  if(right == 10)
  {
    right = 2;
  }
  if(right == 20)
  {
    right = 12;
  }
  return right;
} /* find_right() */

string query_furniture_ordinal(object ob)
{
  int index;
  string str_num;
  object *obs;

  str_num = "";

  obs = match_objects_for_existence(ob->query_plural(), ({ this_object() }));

  index = member_array(ob, obs);

  if((index != -1) && (sizeof(obs) != 1))
  {
    // Cheers to Presto for word_ordinal. :)
    str_num = " " + word_ordinal(index + 1);
  }

  if((index != -1) && (sizeof(obs) != 1))
  {
    // Cheers to Presto for word_ordinal. :)
    str_num = " " + word_ordinal(index + 1);
  }
  
  return str_num;
} /* query_furniture_ordinal() */

/*
 * Should produce "squatting against the eastern wall"
 */
string object_position_desc(object thing)
{
  string verb;
  string other;
  string position;
  string str;
  
  verb = thing->query_property(PLACEMENT_CURRENT_ROOM_VERB);
  other = thing->query_property(PLACEMENT_CURRENT_ROOM_OTHER);
  position = get_pos_desc(thing->query_property(PLACEMENT_CURRENT_ROOM_POSITION));

  if(verb == "")
  {
    str = other + " " + position;
  }
  else
  {
    str = verb[1..] + " " + other + " " + position;
  }  
  return str;
} /* object_position_desc() */

/*
 * Returns a description of how two objects are in relation to
 * each other.
 */
string describe_relative_position(int index)
{
  string relative_desc;
  switch(index)
  {
    case OPPOSITE :
      relative_desc = OPPOSITE_DESCS[random(sizeof(OPPOSITE_DESCS))];
      break;
    case ABOVE :
      relative_desc = ABOVE_DESCS[random(sizeof(ABOVE_DESCS))];
      break;
    case BELOW :
      relative_desc = BELOW_DESCS[random(sizeof(BELOW_DESCS))];
      break;
    case RIGHT :
      relative_desc = RIGHT_DESCS[random(sizeof(RIGHT_DESCS))];
      break;
    case LEFT :
      relative_desc = LEFT_DESCS[random(sizeof(LEFT_DESCS))];
      break;
  }
  return relative_desc;
} /* describe_relative_position() */

/*
 * Should produce "squats a stove" or "is a stove"
 */
string object_desc2(object thing)
{
  string verb;
  string other;
  string position;
  string str;
  mapping allowed_verbs;
  
  verb = thing->query_property(PLACEMENT_CURRENT_ROOM_VERB);
  allowed_verbs = thing->query_allowed_room_verbs();
  if (!allowed_verbs) {
     allowed_verbs = ([ ]);
  }
  verb = allowed_verbs[verb[1..]];
  if(!verb || verb == "")
  {
    verb = "is";
  }
  other = thing->query_property(PLACEMENT_CURRENT_ROOM_OTHER);
  position = get_pos_desc(thing->query_property(PLACEMENT_CURRENT_ROOM_POSITION));
  str = verb + " " + thing->a_short();
  return str;
} /* object_desc2() */

/*
 * Should produce "squatting against the eastern wall is a stove"
 * or "a stove is squatting against the eastern wall".
 */
string object_desc(object thing)
{
  string str;
  /*
   * There may be more ingenious ways of phrasing this.
   */
  switch(random(2))
  {
    case 0 :
      str = object_position_desc(thing) + " is " + thing->a_short();
      break;
    default :
      str = object_position_desc(thing) + " is " + thing->a_short();
      break;
  }  
  return str;
} /* object_desc() */

/**
 * @ignore yes
 *
 * This function returns an array of the indexes which are
 * opposite, above, below, right, and left of the object
 * with the index 'index' in the array 'structure'.  If
 * no object exists in that position a 0 will be inserted.
 */
int *find_relative_indexes(int index, mixed structure)
{
  int *relatives;
  relatives = allocate(5);
  relatives[OPPOSITE] = find_opposite(index, structure);
  relatives[ABOVE] = find_above(index, structure);
  relatives[BELOW] = find_below(index, structure);
  relatives[RIGHT] = find_right(index, structure);
  relatives[LEFT] = find_left(index, structure);
  return relatives;
} /* find_relative_indexes() */

/**
 * @ignore yes
 *
 * This function generates strings like... "Lying in the centre of 
 * the room is a Klatchian rug near which is an armchair.", "Lying 
 * in the centre of the room is a Klatchian rug around which are 
 * three armchairs.", and "Lying in the centre of the room is a 
 * Klatchian rug around which are two armchairs, a sofa, and a 
 * foot stool."
 */
string desc_around_with(object ob, object *around)
{
  // don't list items already described.
  around -= have_described;
  
  if(sizeof(around) == 1)
  {
    return "$C$" + object_desc(ob) + " " +
      around[0]->query_property(PLACEMENT_CURRENT_ROOM_OTHER) +
      " which is " + query_multiple_furniture_short(around) + ".  ";
  }
  return "$C$" + object_desc(ob) + " around which are " + 
    query_multiple_furniture_short(around) + ".  ";
} /* desc_around_with() */

/**
 * @ignore yes
 *
 * This function generates strings like... "Near the Klatchian rug 
 * is an armchair.", "Around the Klatchian rug are three armchairs."
 * and "Around the Klatchian rug are two armchairs, a sofa, and a 
 * foot stool."
 */
string desc_around_without(object *around, object *structure,
  int location)
{
  if(location < 0)
  {
    location = -(location);
  }
  if(!structure[location])
  {
    if(sizeof(around) == 1)
    {
      return "$C$" + around[0]->query_property(PLACEMENT_CURRENT_ROOM_OTHER) 
        + " " + get_pos_desc(location) + " is" +
        query_another(around[0]) + " " + around[0]->query_short() +
        ".  ";
    }
    else
    {
      return "Around " + get_pos_desc(location) + 
        " are " + query_multiple_furniture_short(around) + ".  ";
    }
  }
  else
  {

    if(sizeof(around) == 1)
    {
      return "$C$" + around[0]->query_property(PLACEMENT_CURRENT_ROOM_OTHER) +
        " the" + query_furniture_ordinal(structure[location]) + " " +
        structure[location]->query_short() + " is" +
        query_another(around[0]) + " " +
        around[0]->query_short() + ".  ";
    }
    else
    {
      return "Around " + structure[location]->the_short() + 
        " are " + query_multiple_furniture_short(around) + ".  ";
    }
  }
} /* desc_around_without() */

/**
 * @ignore yes
 *
 * This function removes spaces off the end of a string.
 */
string trim_trailing_spaces(string str)
{
  int i;
  if(!sizeof(str))
  {
    return str;
  }
  for(i = (sizeof(str)) - 1; str[i] == ' '; i--);
  return str[0..i];
} /* trim_trailing_spaces() */

/**
 * @ignore yes
 *
 * This function chooses an object from an array of indexes.  It
 * it doesn't find one it returns zero.
 */
int choose_relative(int *relatives, object *structure) {
  int num, found;
  int *rands;
  
  rands = ({ 0, 1, 2, 3, 4 });
  while(sizeof(rands)) {
    num = rands[random(sizeof(rands))];
    if(objectp(structure[relatives[num]])) {
      if(member_array(structure[relatives[num]], needs_described) != -1) {
        found = 1;
        break;
      }
    }
    rands -= ({ num });
  }
  if(found)
    return num;
  return -1;
} /* choose_relative() */

/**
 * @ignore yes
 *
 * This function returns the objects placed around the specified
 * object.
 */
object *get_around(object ob) {
  object *around;
  around = filter(all_inventory(this_object()), 
    (: $1->query_property(PLACEMENT_CURRENT_ROOM_POSITION) &&
       $1->query_property(PLACEMENT_CURRENT_ROOM_POSITION) == 
       -($(ob->query_property(PLACEMENT_CURRENT_ROOM_POSITION))) :));
  return around;
} /* get_around() */

void set_orientation(int num)
{
  orientation = num;
} /* set_orientation() */

int query_orientation()
{
  return orientation;
} /* set_orientation() */

string wall_or_corner(int pos)
{
   return query_wall_name_pos(pos);
} /* wall_or_corner() */

/**
 * @ignore yes
 *
 * This function returns a string describing objects in relation
 * to each other and the objects placed around them.
 */
string desc_obs_relatively(int *indexes, object *structure)
{
  int i;
  int *relatives;
  string desc;
  object *around;
  int rel_num;
  object rel_ob;

  desc = "";
  while(sizeof(indexes)) {
    i = indexes[random(sizeof(indexes))];

    // Remove object object's index from the indexes array.
    indexes -= ({ i });

    // Make absolutely sure the object hasn't been described before.
    if(member_array(structure[i], needs_described) == -1) {
      continue;
    }

    // Remove the object from the needs described array.
    needs_described -= ({ structure[i] });
    have_described += ({ structure[i] });

    // Find out what we've got relative to us.
    relatives = find_relative_indexes(i, structure);
    
    // Pick one of the things we've got relative to us.
    rel_num = choose_relative(relatives, structure);
    if(rel_num == -1)
      rel_ob = 0;
    else
      rel_ob = structure[relatives[rel_num]];
    
    // Find out what we've got around us.
    around = get_around(structure[i]);

    // Check if we have an object relative to this object.
    if(!rel_ob) {
      // Check if we've got anything placed around this object.
      if(sizeof(around)) {
        // Generate a string describing the location of this object
        // and what has been placed around it.
        desc += desc_around_with(structure[i], around);
        
        // Remove the around objects from the needs described array.
        needs_described -= around;
        have_described += around;
        continue;
      } else {
        if(clutter_string(structure[i]) == "") {
          // Nothing to work with so just describe the object.
          desc += "$C$" + object_desc(structure[i]) + ".  ";
        } else {
          desc += "$C$" + object_desc(structure[i]) + 
            " upon which" + clutter_string(structure[i]) + ".  ";
        }
        continue;
      }
    }

    // Remove the relative object from the needs described array.
    needs_described -= ({ rel_ob });
    have_described += ({ rel_ob });

    // Remove the relative object's index from the indexes array.
    indexes -= ({ relatives[rel_num] });
    
    // Build the sentence described the two objects relations to
    // each other.
    desc += "$C$" + object_desc(structure[i]) + " " + 
      describe_relative_position(rel_num) + " " + 
      object_desc2(rel_ob) + ".  ";

    // Check for objects on the first object.
    
    if(clutter_string(structure[i]) != "") {
      desc += "On " + structure[i]->the_short() + 
        clutter_string(structure[i]);
      if(clutter_string(rel_ob) != "") {
        // It was here that Gototh realised he was totally pissed,
        // but carried on regardless.
        desc += " while on " + rel_ob->the_short() + 
          clutter_string(rel_ob) + ".  ";
      } else {
        desc += ".  ";
      }
    } else if(clutter_string(rel_ob) != "") {
      desc += "On " + rel_ob->the_short() + 
        clutter_string(rel_ob) + ".  ";
    }

    // Check for objects placed around the first object.
    if(sizeof(around)) {
      // Describe objects around the first object.
      desc += desc_around_without(around, structure, i);
      needs_described -= around;
      have_described += around;
      continue;
    }
    
    // Find out what objects are around the second object.
    around = get_around(rel_ob);

    // Check for objects placed around the second object.
    if(sizeof(around)) {
      // Describe objects around the second object.
      desc += desc_around_without(around, structure, relatives[rel_num]);
      needs_described -= around;
      have_described += around;
      continue;
    }
  }
  return desc;
} /* desc_objects_relatively() */

int room_order(object *obs1, object *obs2, object *structure)
{
  int pos1, pos2;
  object ob1, ob2;
  int order1, order2;

  pos1 = -(obs1[0]->query_property(PLACEMENT_CURRENT_ROOM_POSITION));
  pos2 = -(obs2[0]->query_property(PLACEMENT_CURRENT_ROOM_POSITION));

  if (pos1 < 0) {
     ob1 = 0;
  } else {
     ob1 = structure[pos1];
  }
  if (pos2 < 0) {
     ob2 = 0;
  } else {
     ob2 = structure[pos2];
  }

  if(!ob1 || !ob2)
  {
    return 0;
  }

  if(ob1->query_plural() != ob2->query_plural())
  {
    return 0;
  }

  order1 = member_array(ob1, match_objects_for_existence(ob1->query_plural(), 
    ({ this_object() })));
  order2 = member_array(ob2, match_objects_for_existence(ob2->query_plural(), 
    ({ this_object() })));

  if(order1 > order2)
  {
    return 1;
  }
  return -1;
} /* room_order() */

/**
 * @ignore yes
 *
 * This function describes objects which have been placed around
 * room locations or objects which are no longer there.
 */
string describe_remaining_objects(object *structure)
{
  int pos;
  string desc;
  object *group;
  mixed *orphans;
  desc = "";

  if(sizeof(needs_described))
  {
    // Group them into arrays depending on where they're positioned.
    orphans = unique_array(needs_described, 
      (: $1->query_property(PLACEMENT_CURRENT_ROOM_POSITION) :));

    if(sizeof(orphans))
    {
      orphans = sort_array(orphans, (: room_order($1, $2, $(structure)) :));

      foreach(group in orphans)
      {
        // Find out where they're positioned.
        pos = group[0]->query_property(PLACEMENT_CURRENT_ROOM_POSITION);
        
        // Described where they're positioned.
        desc += desc_around_without(group, structure, pos);
        
        // Remove them from the need described array.
        needs_described -= group;
        have_described += group;
      }
    }
  }
  return desc;
} /* describe_remaining_objects() */

string get_group_verb(object *obs)
{
  mixed *verbs;
  
  verbs = map(obs, (: $1->query_property(PLACEMENT_CURRENT_ROOM_VERB) :));
  verbs = unique_array(verbs, (: $1 :));
  
  if(sizeof(verbs) == 1)
  {
    return verbs[0][0];
  }
  return "";  
} /* query_same_verb() */

string get_group_other(object *obs)
{
  mixed *others;
  
  others = map(obs, (: $1->query_property(PLACEMENT_CURRENT_ROOM_OTHER) :));
  others = unique_array(others, (: $1 :));
  
  if(sizeof(others) == 1)
  {
    return others[0][0];
  }
  // Can't think of a good way to do this.
  return others[random(sizeof(others))][0];
} /* query_same_other() */

string describe_plural_objects(int *indexes, object *structure)
{
  int i, j;
  int flag;
  int rel_num;
  int used_pos;
  mixed *things;
  string desc = "";
  string *strs;
  
  strs = ({ });
  things = ({ });
  // Check for multiple objects with the same plural.
  for(i = 0; i < sizeof(indexes); i++) {
    things += ({ structure[indexes[i]] });
  }
  things = unique_array(things, (: $1->query_plural() :));
  things = filter(things, (: sizeof($1) > 1 :) );

  if(sizeof(things) > 0) {
    // Deal with these and remove them from the list.
    for(i = 0; i < sizeof(things); i++) {

      needs_described -= things[i];
      have_described += things[i];

      // Find out if there's four of them.
      if(sizeof(things[i]) == 4) {
        int they_are = 1;
        int *rel_nums;
        int num;

        rel_nums = map(things[i],
          (: $1->query_property(PLACEMENT_CURRENT_ROOM_POSITION) :));
#ifdef DEBUG
        debug_printf("rel_nums: %O", rel_nums);
#endif        
        // Find out if they're in each corner.
        foreach(num in rel_nums)
          if(is_in_corner(num))
            they_are = 0;

        if(they_are) {        
          desc += "$C$" + things[i][0]->query_plural() + " are" +
            get_group_verb(things[i]) + " " + 
            get_group_other(things[i]) + " each " + 
            wall_or_corner(rel_nums[0]) + ".  ";
#ifdef DEBUG
          debug_printf("4 corner: %s\n", desc);
#endif
          continue;
        }

        they_are = 1;

        // Find out if they're at each wall.
        foreach(num in rel_nums)
        {
          if(is_beside_wall(num))
          {
            they_are = 0;
          }
        }
        if(they_are)
        {
          desc += "$C$" + things[i][0]->query_plural() + " are" +
            get_group_verb(things[i]) + " " + 
            get_group_other(things[i]) + " each " + 
            wall_or_corner(3) + ".  ";
          continue;
        }
      }

      if(sizeof(things[i]) == 3)
      {
        int *rel_nums;

        rel_nums = map(things[i], 
          (: $1->query_property(PLACEMENT_CURRENT_ROOM_POSITION) :));
        rel_nums = sort_array(rel_nums, 1);

        if(rel_nums[2] == 9)
        {
          rel_nums -= ({ 9 });
          rel_nums = ({ 1 }) + rel_nums;
        }
        
        if(is_beside_wall(rel_nums[1]) && 
          rel_nums[0] == rel_nums[1] - 1 &&
          rel_nums[2] == rel_nums[1] + 1)
        {
          desc += "$C$" + query_multiple_short(things[i]) +
            " are" + get_group_verb(things[i]) + " beside each other " +
            structure[rel_nums[1]]->query_property(PLACEMENT_CURRENT_ROOM_OTHER)
            + " the " + get_pos_desc(rel_nums[1]) + ".  ";
          continue;
        }
      }

      // Find out if there's two of them.
      if(sizeof(things[i]) == 2)
      {
        // Find out if they're positioned relative to each other.
        rel_num = member_array(get_structure_index(things[i][1]), 
          find_relative_indexes(get_structure_index(things[i][0]), 
          structure));
        if(rel_num != -1)
        {
          desc += "$C$" + query_multiple_short(things[i]);
          switch(rel_num)
          {
            case OPPOSITE :
              desc += " face each other across the room.  ";
              continue;
              break;
            case RIGHT :
            case LEFT :
              desc += " are" + get_group_verb(things[i]) + 
                " beside each other ";
              break;
            default :
              break;
          }
          if(!orientation)
          {
            // Check if it's odd.
            if(get_structure_index(things[i][0]) & 1)
            {
              used_pos = 
                things[i][1]->query_property(PLACEMENT_CURRENT_ROOM_POSITION);
            }
            else
            {
              used_pos = 
                things[i][0]->query_property(PLACEMENT_CURRENT_ROOM_POSITION);
            }
          }
          else
          {
            // Check if it's odd.
            if(get_structure_index(things[i][0]) & 1)
            {
              used_pos = 
                things[i][0]->query_property(PLACEMENT_CURRENT_ROOM_POSITION);
            }
            else
            {
              used_pos = 
                things[i][1]->query_property(PLACEMENT_CURRENT_ROOM_POSITION);
            }
          }
          desc += 
            structure[used_pos]->query_property(PLACEMENT_CURRENT_ROOM_OTHER) +
            " the " + get_pos_desc(used_pos) + ".  ";
          continue;
        }
      }

      desc += "$C$" + query_num(sizeof(things[i]), 4) + " "
        + things[i][0]->query_plural() + " are positioned "
        "around the room.  ";
#ifdef DEBUG
      debug_printf("2 items: %s\n", desc);
#endif

      // If there's more than four say no more about them.
      if(sizeof(things[i]) > 4)
      {
        flag = 1;
      }
      else
      {
        desc += "They are ";
      }

      for(j = 0; j < sizeof(things[i]); j++)
      {
        if(!flag)
        {
          strs += ({ object_position_desc(things[i][j]) });
        }
        indexes -= 
          ({ things[i][j]->query_property(PLACEMENT_CURRENT_ROOM_POSITION) });
      }
      
      if(!flag)
      {
        desc += query_multiple_short(strs)+".  ";
#ifdef DEBUG
        debug_printf("position: %s [%s]\n",
                     query_multiple_short(strs), desc);
#endif
        strs = ({ });
      }
      
    }
  }
  return desc;
} /* describe_plural_objects() */

string furniture_long()
{
  string desc, d1, d2, d3;
  object *structure;
  int i, *indexes;

  if(furniture_long != "") {
    return furniture_long;
  }
  
  structure = build_structure();
  indexes = ({ });

  // Build an array of indexes where objects are stored.
  for(i = 0; i < sizeof(structure); i++)
  {
    if(objectp(structure[i]))
    {
      indexes += ({ i });
    }
  }

  needs_described = filter(all_inventory(this_object()), 
    (: $1->query_property(PLACEMENT_CURRENT_ROOM_POSITION) :));

  have_described = ({ });

  d1 = describe_plural_objects(indexes, structure);
  d2 = desc_obs_relatively(indexes, structure);
  d3 = describe_remaining_objects(structure);
#ifdef DEBUG
  debug_printf("1: %s\n2:%s\n3:%s\n",d1, d2, d3);
#endif
  desc = d1 + d2 + d3;

  if(desc == "")
    desc = "$C$" + this_object()->the_short() + "$C$" + " is completely empty.";
  furniture_long = trim_trailing_spaces(desc);
  return furniture_long;
} /* furniture_long() */

/**
 * @ignore yes
 *
 * This function returns a string describing the location of
 * the index in the structure array.
 */
string get_pos_desc(int pos_int)
{
  string str;
  switch(pos_int)
  {
    case 1 :
      str = "the centre of " + this_object()->the_short();
      break;
    case 2 :
    case 12 :
      str = "the north " + wall_or_corner(pos_int);
      break;
    case 3 :
    case 13 :
      str = "the northeast " + wall_or_corner(pos_int);
      break;
    case 4 :
    case 14 :
      str = "the east " + wall_or_corner(pos_int);
      break;
    case 5 :
    case 15 :
      str = "the southeast " + wall_or_corner(pos_int);
      break;
    case 6 :
    case 16 :
      str = "the south " + wall_or_corner(pos_int);
      break;
    case 7 :
    case 17 :
      str = "the southwest " + wall_or_corner(pos_int);
      break;
    case 8 :
    case 18 :
      str = "the west " + wall_or_corner(pos_int);
      break;
    case 9 :
    case 19 :
      str = "the northwest " + wall_or_corner(pos_int);
      break;
    case 11 :
      str = "the centre of the ceiling";
      break;
  }
  return str;
} /* get_pos_desc() */

/**
 * @ignore yes
 *
 * This builds a structure based on what things are in the room so
 * that it can be determined where the objects are in relation to
 * one another.
 *
 *   19---12---13
 *    |\        \
 *    | 18  11   14
 *    |  \        \
 *    |  17---16---15
 *    |
 *    9----2----3
 *     \         \
 *      8    1    4
 *       \         \
 *        7----6----5
 */
object *build_structure()
{
  int i;
  object *positions, *obs, *dummies;

  positions = allocate(20);
  obs = all_inventory(this_object());
  for(i = 1; i < sizeof(positions); i++)
  {
    dummies = filter(obs, 
      (: $1->query_property(PLACEMENT_CURRENT_ROOM_POSITION) == $(i) :));
    if(sizeof(dummies))
    {
      positions[i] = dummies[0];
    }
  }
  return positions;
} /* build_structure() */

/**
 * @ignore yes
 *
 * This function returns the position of the object in the
 * structure array.
 */
int get_structure_index(object ob)
{
  return member_array(ob, build_structure());
} /* get_structure_index() */

/**
 * @ignore yes
 *
 * This function is used from describing the individual walls,
 * corners, floor and ceiling for use in add_item()s.
 */
string desc_surface(string dir)
{
  int i;
  int pos;
  int *indexes;
  int current_pos;
  string desc;
  object *structure;
  object *around;
  
  structure = build_structure();
  needs_described = ({ });
  have_described = ({ });
  
  if(dir == "ceiling")
  {
    pos = 11;
  }
  else
  {
    pos = get_pos_int(dir);
  }
  desc = "";
  indexes = ({ });
  
  for(i = 1; i < sizeof(structure); i++)
  {
    if(!objectp(structure[i]))
    {
      continue;
    }
    current_pos = structure[i]->query_property(PLACEMENT_CURRENT_ROOM_POSITION);
    if(current_pos != pos)
    {
      if(pos != 1 && pos != 11)
      {
        if((current_pos != pos + 10) && (current_pos != pos - 10))
        {
          structure[i] = 0;
        }
        else
        {
          needs_described += ({ structure[i] });
          indexes += ({ i });
        }
      }
    }
    else
    {
      needs_described += ({ structure[i] });
      around = get_around(structure[i]);
      if(around)
      {
        needs_described += around;
      }
      indexes += ({ i });
    }
  }

  desc += desc_obs_relatively(indexes, structure);

  return desc;
} /* desc_surface() */

void displace_object(object ob)
{
  // Make sure it is in the room first.
  if (ob->query_property(PLACEMENT_CURRENT_ROOM_POSITION)) {
    ob->remove_property(PLACEMENT_CURRENT_ROOM_VERB);
    ob->remove_property(PLACEMENT_CURRENT_ROOM_OTHER);
    ob->remove_property(PLACEMENT_CURRENT_ROOM_POSITION);
    // Make sure the long is fixed up.
    furniture_long = "";
  }
} /* displace_object() */

int do_displace(object *obs)
{
  if(this_player() &&
     !this_object()->test_occupier(this_player()->query_name()) &&
     !this_player()->query_creator()) {
    this_player()->add_failed_mess(this_object(),
                                   "You don't own this house.\n");
    return 0;
  }

  if(sizeof(obs) > 1)
  {
    this_player()->add_succeeded_mess(this_object(),
      "You may only displace one thing at a time.\n", ({ }));
    return 1;
  }

  if(!obs[0]->query_property(PLACEMENT_CURRENT_ROOM_POSITION))
  {
    this_player()->add_failed_mess(this_object(),
      "$C$" + obs[0]->the_short() + " is not placed.\n", ({ }));
    return 0;
  }

  if(obs[0]->query_property(PLACEMENT_IMMOVABLE)) {
    this_player()->add_failed_mess(this_object(),
                                   "Try as you might you cannot move $I.\n",
                                   ({ obs[0] }));
    return 0;
  }
  
  displace_object(obs[0]);
  this_player()->add_succeeded_mess(this_object(),
    "$N drag$s " + obs[0]->a_short() + " out of position.\n", ({ }));
  furniture_long = "";
  return 1;
} /* do_displace() */

/**
 * @ignore yes
 *
 * This removes the placement properties from an object when it is
 * removed from the room.
 */
void event_exit(object ob, string, object) 
{
  displace_object(ob);
} /* event_exit() */

int test_remove(object thing, int flag, mixed dest) {
  if(thing->query_property(PLACEMENT_IMMOVABLE))
    return 0;

  // Let stuff that's kind of placed (eg. curtains) be notified when they
  // are removed.
  if(thing->query_property(PLACEMENT_CURRENT_ROOM_HIDDEN))
    thing->removed();
  return 1;
}