/**************************************************************************
*  File: asciimap.c                                        Part of tbaMUD *
*  Usage: Generates an ASCII map of the player's surroundings.            *
*                                                                         *
*  All rights reserved.  See license for complete information.            *
*                                                                         *
*  Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
*  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
**************************************************************************/

#include "conf.h"
#include "sysdep.h"

#include "structs.h"
#include "utils.h"
#include "comm.h"
#include "interpreter.h"
#include "handler.h"
#include "db.h"
#include "spells.h"
#include "house.h"
#include "constants.h"
#include "dg_scripts.h"
#include "asciimap.h"

/******************************************************************************
 * Begin Local (File Scope) Defines and Global Variables
 *****************************************************************************/
/* Do not blindly change these values, as many values cause the map to stop working - backup first */
#define CANVAS_HEIGHT       19
#define CANVAS_WIDTH        51
#define LEGEND_WIDTH        15

#define DEFAULT_MAP_SIZE    CONFIG_MAP_SIZE

#define MAX_MAP_SIZE        (CANVAS_WIDTH - 1)/4
#define MAX_MAP             CANVAS_WIDTH

#define SECT_EMPTY          30 /* anything greater than num sect types */
#define SECT_HERE           (SECT_EMPTY + 1)
#define OUT_OF_ZONE         (SECT_EMPTY + 2) /* Keep this one last, add before */

/* This macro offsets the sector by 50 in the map array to 
 * prevent conflicts. (because NORTH = 0 and SECT_INSIDE = 0) */
#define SECT_OFFSET(sect)   (sect >= 50 ? (sect - 50) : (sect + 50))

#define DOOR_N              NORTH         // 0
#define DOOR_E              EAST
#define DOOR_S              SOUTH
#define DOOR_W              WEST
#define DOOR_U              UP
#define DOOR_D              DOWN
#define DOOR_NE             NORTHEAST
#define DOOR_NW             NORTHWEST
#define DOOR_SW             SOUTHWEST
#define DOOR_SE             SOUTHEAST
#define DOORS_CONNECTED     10      
#define DOOR_NS             (NORTH     + DOORS_CONNECTED)   // 10
#define DOOR_EW             (EAST      + DOORS_CONNECTED)
#define DOOR_SN             (SOUTH     + DOORS_CONNECTED)
#define DOOR_WE             (WEST      + DOORS_CONNECTED)
#define DOOR_UD             (UP        + DOORS_CONNECTED)
#define DOOR_DU             (DOWN      + DOORS_CONNECTED)
#define DOOR_NESW           (NORTHEAST + DOORS_CONNECTED)
#define DOOR_NWSE           (NORTHWEST + DOORS_CONNECTED)
#define DOOR_SWNE           (SOUTHWEST + DOORS_CONNECTED)
#define DOOR_SENW           (SOUTHEAST + DOORS_CONNECTED)
#define DOORS_NOT_CONNECTED (DOORS_CONNECTED*2)
#define DOOR_NXS            (NORTH     + DOORS_NOT_CONNECTED)   // 20
#define DOOR_EXW            (EAST      + DOORS_NOT_CONNECTED)
#define DOOR_SXN            (SOUTH     + DOORS_NOT_CONNECTED)
#define DOOR_WXE            (WEST      + DOORS_NOT_CONNECTED)
#define DOOR_UXD            (UP        + DOORS_NOT_CONNECTED)
#define DOOR_DXU            (DOWN      + DOORS_NOT_CONNECTED)
#define DOOR_NEXSW          (NORTHEAST + DOORS_NOT_CONNECTED)
#define DOOR_NWXSE          (NORTHWEST + DOORS_NOT_CONNECTED)
#define DOOR_SWXNE          (SOUTHWEST + DOORS_NOT_CONNECTED)
#define DOOR_SEXNW          (SOUTHEAST + DOORS_NOT_CONNECTED)
#define DOORS_SPECIAL       (DOORS_CONNECTED*3)
#define DOOR_N_U            30  /* Specials */
#define DOOR_N_D            31
#define DOOR_N_UD           32
#define DOOR_S_U            33
#define DOOR_S_D            34
#define DOOR_S_UD           35
#define DOOR_NS_U           36
#define DOOR_NS_D           37
#define DOOR_NS_UD          38
#define DOOR_NXS_U          39
#define DOOR_NXS_D          40
#define DOOR_NXS_UD         41
#define DOOR_CROSS          42

#define DOOR_NONE           43 

#define BASE_DIR(dir)       (dir >= DOORS_SPECIAL ? dir : \
                              (dir >= DOORS_NOT_CONNECTED ? (dir - DOORS_NOT_CONNECTED) : \
                              (dir >= DOORS_CONNECTED ? (dir - DOORS_CONNECTED) : dir)))

#define MAP_CIRCLE          0
#define MAP_RECTANGLE       1

#define MAP_NORMAL          0
#define MAP_COMPACT         1

#define MAPPED              0
#define ROOM                1

/* Prefixes/suffixes to mark rooms in/our of current zone */
#define INZONE_BEGIN        "\tc["
#define INZONE_END          "\tc]\tn"
#define OUTZONE_BEGIN       "\tR["
#define OUTZONE_END         "\tR]\tn"

static bool show_worldmap(struct char_data *ch);

struct map_info_type
{
  int  sector_type;   /* Door or Sector type */
  char disp[20];      /* Normal Text  */
  char alt[20];       /* Compact Text */
};

static struct map_info_type door_info[] =
{ /* Exit - Other room not mapped */                  
                      /* Normal */      /* Compact */
  { DOOR_N,          " . "            , " . "             },  // 0
  { DOOR_E,          "-  "            , ">"               },     
  { DOOR_S,          " ' "            , " ' "             },     
  { DOOR_W,          "  -"            , "<"               },    
  { DOOR_U,          "  \tr+\tn"      , "  \tr+\tn"       },    
  { DOOR_D,          "\tr-\tn  "      , "\tr-\tn  "       },  // 5   
  { DOOR_NW,         " \\ "           , "\\"              },  // currently unused
  { DOOR_NE,         " / "            , "/"               },  // currently unused   
  { DOOR_SE,         " \\ "           , "\\"              },  // currently unused  
  { DOOR_SW,         " / "            , "/"               },  // currently unused
  /* Exit - Connected to adjacent room */                                       
  { DOOR_NS,         " | "            , " | "             },  // 10
  { DOOR_EW,         "---"            , "-"               },    
  { DOOR_SN,         " | "            , " | "             },   
  { DOOR_WE,         "---"            , "-"               },   
  { DOOR_UD,         "\tr- +\tn"      , "\tr- +\tn"       },   
  { DOOR_DU,         "\tr- +\tn"      , "\tr- +\tn"       },  // 15  
  { DOOR_NWSE,       " \\ "           , "\\"              }, 
  { DOOR_NESW,       " / "            , "/"               }, 
  { DOOR_SWNE,       " / "            , "/"               },  // currently unused
  { DOOR_SENW,       " \\ "           , "\\"              },  // currently unused
  /* Exit - Not connected to adjacent room */                                     
  { DOOR_NXS,        " : "            , " : "             },  // 20
  { DOOR_EXW,        "- -"            , "X"               },   
  { DOOR_SXN,        " : "            , " : "             },  
  { DOOR_WXE,        "- -"            , "X"               },  
  { DOOR_UXD,        "\tr+\tn  "      , "\tr+\tn"         },  
  { DOOR_DXU,        "  \tr-\tn"      , "\tr-\tn"         },  // 25
  { DOOR_NWXSE,      " \\ "           , "\\"              },  // currently unused
  { DOOR_NEXSW,      " / "            , "/"               },  // currently unused
  { DOOR_SWXNE,      " / "            , "/"               },  // currently unused
  { DOOR_SEXNW,      " \\ "           , "\\"              },  // currently unused
  /* Specials  - Combos */
  { DOOR_N_U,        " \tn.\tr+\tn"   , " \tn.\tr+\tn"    },  // 30
  { DOOR_N_D,        "\tr-\tn. "      , "\tr-\tn. "       },  // 31
  { DOOR_N_UD,       "\tr-\tn.\tr+\tn", "\tr-\tn.\tr+\tn" },  // 32
  { DOOR_S_U,        " \tn'\tr+\tn"   , " \tn'\tr+\tn"    },  // 33
  { DOOR_S_D,        "\tr-\tn' "      , "\tr-\tn' "       },  // 34
  { DOOR_S_UD,       "\tr-\tn'\tr+\tn", "\tr-\tn'\tr+\tn" },  // 35
  { DOOR_NS_U,       " \tn|\tr+\tn"   , " \tn|\tr+\tn"    },  // 36
  { DOOR_NS_D,       "\tr-\tn| "      , "\tr-\tn| "       },  // 37
  { DOOR_NS_UD,      "\tr-\tn|\tr+\tn", "\tr-\tn|\tr+\tn" },  // 38
  { DOOR_NXS_U,      " \tn:\tr+\tn"   , " \tn:\tr+\tn"    },  // 39
  { DOOR_NXS_D,      "\tr-\tn: "      , "\tr-\tn: "       },  // 40
  { DOOR_NXS_UD,     "\tr-\tn:\tr+\tn", "\tr-\tn:\tr+\tn" },  // 41
  { DOOR_CROSS,      " X "            , "X"               },  // 42
  { DOOR_NONE,       "   "            , " "               }   // 43

};

/* This is used to track which rooms and their exits have been scanned. 
 * To prevent changing the function calls, I went with a global pointer
 * and an integer to track to the top of the list. I don't really like 
 * globals, but it seems to make sense in this scenario.
 * -Doogie */

struct roomscan
{
  room_rnum room;
  int x_offset;
  int y_offset;
  room_rnum exits[NUM_OF_DIRS][2]; /* MAPPED = Mapped Y/N? / ROOM = room rnum */
} *roomlist = NULL;

int roomlist_top; /* Tracking for the roomscan array */

static struct map_info_type map_info[] =
{
  { SECT_INSIDE,       "\tnI"     }, /* 0 */
  { SECT_CITY,         "\tDC"     },
  { SECT_FIELD,        "\tgP"     },
  { SECT_FOREST,       "\tyF"     },
  { SECT_HILLS,        "\tMH"     }, 
  { SECT_MOUNTAIN,     "\trM"     }, /* 5 */
  { SECT_WATER_SWIM,   "\tc~"     },
  { SECT_WATER_NOSWIM, "\tb="     },
  { SECT_FLYING,       "\tC^"     },
  { SECT_UNDERWATER,   "\tbU"     }, 
  { -1,                ""         }, /* 10 */
  { -1,                ""         },
  { -1,                ""         },
  { -1,                ""         },
  { -1,                ""         }, 
  { -1,                ""         },  /* 15 */
  { -1,                ""         },
  { -1,                ""         },
  { -1,                ""         },
  { -1,                ""         },
  { -1,                ""         }, /* 20 */
  { -1,                ""         },
  { -1,                ""         },
  { -1,                ""         },
  { -1,                ""         },
  { -1,                ""         }, /* 25 */
  { -1,                ""         },
  { -1,                ""         },
  { -1,                ""         },
  { -1,                ""         }, 
  { SECT_EMPTY,        " "        }, /* 30 */
  { SECT_HERE,         "\tn\tB!"  },
  { OUT_OF_ZONE,       "\tRX"     } /* Add before; Keep this one last - You should never see this*/
};

/* map - 3 dimensions: 1.) map coords or room number 2.) x 3.) y */
static int map[2][MAX_MAP][MAX_MAP];

                            /*   N       E       S       W       U    */
static int offsets[10][2] = { {-2, 0},{ 0, 2},{ 2, 0},{ 0,-2},{ 0, 0},
                              { 0, 0},{-2,-2},{-2, 2},{ 2, 2},{ 2,-2} };
                            /*   D       NW      NE      SE      SW   */
static int offsets_worldmap[10][2] = {
                            /*   N       E       S       W       U    */ 
                              {-1, 0},{ 0, 1},{ 1, 0},{ 0,-1},{ 0, 0},
                              { 0, 0},{-1,-1},{-1, 1},{ 1, 1},{ 1,-1}  };
                            /*   D       NW      NE      SE      SW   */
static int door_offsets[10][2] = { 
                            /*   N       E       S       W       U    */
                              {-1, 0},{ 0, 1},{ 1, 0},{ 0,-1},{-1, 0},
                              {-1, 0},{-1,-1},{-1, 1},{ 1, 1},{ 1,-1} };
                            /*   D       NW      NE      SE      SW   */
                            
                           /*   N        E          S            W        U    */
static int door_marks[]= { DOOR_N,    DOOR_E,     DOOR_S,    DOOR_W,    DOOR_U, 
                           DOOR_D,    DOOR_NW,    DOOR_NE,   DOOR_SE,   DOOR_SW,
                           /*   D        NW         NE           SE       SW   */      
                                      
      /*    Connected   */ DOOR_NS,   DOOR_EW,    DOOR_NS,   DOOR_EW,   DOOR_UD, 
                           DOOR_DU,   DOOR_NWSE,  DOOR_NESW, DOOR_NWSE, DOOR_NESW,
                                      
      /*  Not Connected */ DOOR_NXS,  DOOR_EXW,   DOOR_SXN,  DOOR_WXE,  DOOR_UXD, 
                           DOOR_DXU,  DOOR_NWXSE, DOOR_NEXSW,DOOR_NWXSE,DOOR_NEXSW,
                                      
      /*     Specials   */ DOOR_N_U,  DOOR_N_D,   DOOR_N_UD, DOOR_S_U,  DOOR_S_D,                       
                           DOOR_S_UD, DOOR_NS_U,  DOOR_NS_D, DOOR_NS_UD,DOOR_NXS_U,
                           DOOR_NXS_D,DOOR_NXS_UD,DOOR_CROSS }; 
                              
/******************************************************************************
 * End Local (File Scope) Defines and Global Variables
 *****************************************************************************/

/******************************************************************************
 * Begin Local (File Scope) Function Prototypes
 *****************************************************************************/
static int  MapRoom(struct char_data *ch, room_rnum room, int x, int y);
static int  MapDoor(int level, room_rnum room, sh_int door, struct room_direction_data *pexit, int x, int y);
static void ExploreRoom(struct char_data *ch, int level, room_rnum room, int *x, int *y, bool worldmap); 
static void MapArea(room_rnum room, struct char_data *ch, int x, int y, int min, int max, bool worldmap);
static char *StringMap(int centre, int size, sh_int level);
static char *WorldMap(int centre, int size, int mapshape, int maptype, sh_int level);
static char *CompactStringMap(int centre, int size);
static void perform_map( struct char_data *ch, char *argument, bool worldmap );
static int  createroomlist(int size);
static void add_door(int x, int y, int door);
/******************************************************************************
 * End Local (File Scope) Function Prototypes
 *****************************************************************************/

int createroomlist(int size)
{
  if (size <= 0 && roomlist != NULL) {
    free(roomlist);
    roomlist_top = 0;
    roomlist = NULL;
  }
  else {
    CREATE(roomlist, struct roomscan, size);
    roomlist_top = size - 1;   
    roomlist[roomlist_top].room = NOWHERE;
    return size;
  }
  return 0;
}

bool can_see_map(struct char_data *ch) {
  /* Is the map funcionality disabled? */
  if (CONFIG_MAP == MAP_OFF)
    return FALSE;
  else if ((CONFIG_MAP == MAP_IMM_ONLY) && (GET_LEVEL(ch) < LVL_IMMORT))
    return FALSE;

  return TRUE;
}

static void add_door(int x, int y, int door)
{
  int door_to;
  
  if (door_marks[door] == -map[MAPPED][x][y])
    return; 

  door_to  = BASE_DIR(door);

  /* diagonals will always be shown connected */
  if (door_to == DOOR_SE || door_to == DOOR_NW) {
    door = DOOR_NWSE;
    door_to = DOOR_NW;
  }
  else if (door_to == DOOR_SW || door_to == DOOR_NE) {
    door = DOOR_NESW;
    door_to = DOOR_NE;
  }
  
  /* This is required to handle the ups/downs 
   * This would all probably work better as a bitvector system in which
   * exits are tagged on as they are encountered resulting in a final value that
   * correlates with a graphic that respresents all of the idividual parts of the
   * exit. */
  
  if (-map[MAPPED][x][y] != DOOR_NONE && map[MAPPED][x][y] != SECT_OFFSET(SECT_EMPTY)) {
    switch (door_to)
    {
      case DOOR_U:
      case DOOR_D:
        switch (-map[MAPPED][x][y])
        {
          case DOOR_NS_UD:
            return;
          case DOOR_U:
          case DOOR_D:
            door = DOOR_UD;
            break;
          case DOOR_N:
            if (door_to == DOOR_U)
              door = DOOR_N_U;
            else if (door_to == DOOR_D)
              door = DOOR_N_D;
            break;
          case DOOR_S:
            if (door_to == DOOR_U)
              door = DOOR_S_U;
            else if (door_to == DOOR_D)
              door = DOOR_S_D;
            break;
          case DOOR_NS:
          case DOOR_SN:
            if (door_to == DOOR_U)
              door = DOOR_NS_U;
            else if (door_to == DOOR_D)
              door = DOOR_NS_D;
            break;
          case DOOR_NS_U:
          case DOOR_NS_D:
            if (door_to == DOOR_D || door_to == DOOR_U)
              door = DOOR_NS_UD;
            break;
          case DOOR_NXS:
          case DOOR_SXN:
            if (door_to == DOOR_U)
              door = DOOR_NXS_U;
            else if (door_to == DOOR_D)
              door = DOOR_NXS_D;
            break;
          case DOOR_NXS_U:
          case DOOR_NXS_D:
            if (door_to == DOOR_D || door_to == DOOR_U)
              door = DOOR_NXS_UD;
            break;
          default:
            break;
        }
        break;
      case DOOR_N:
        switch (-map[MAPPED][x][y])
        {
          case DOOR_N:        /* Don't overwrite */
          case DOOR_NS:
          case DOOR_SN:
          case DOOR_NXS:
          case DOOR_SXN:
          case DOOR_N_U:
          case DOOR_N_D:
          case DOOR_N_UD:
          case DOOR_NS_U:
          case DOOR_NS_D:
          case DOOR_NS_UD:
          case DOOR_NXS_U:
          case DOOR_NXS_D:
          case DOOR_NXS_UD:
            return;
          case DOOR_S_U:
            if (door == DOOR_N || door == DOOR_NXS)
              door = DOOR_NXS_U;
            else 
              door = DOOR_NS_U;
            break;
          case DOOR_S_D:
            if (door == DOOR_N || door == DOOR_NXS)
              door = DOOR_NXS_D;
            else 
              door = DOOR_NS_D;
            break;
          case DOOR_S_UD:
            if (door == DOOR_N || door == DOOR_NXS)
              door = DOOR_NXS_UD;
            else 
              door = DOOR_NS_UD;
            break;          
          case DOOR_U:
            if (door == DOOR_N)
              door = DOOR_N_U;
            else if (door == DOOR_NS)
              door = DOOR_NS_U;
            else if (door == DOOR_NXS)
              door = DOOR_NXS_U;
            break;             
          case DOOR_D:
            if (door == DOOR_N)
              door = DOOR_N_D;
            else if (door == DOOR_NS)
              door = DOOR_NS_D;
            else if (door == DOOR_NXS)
              door = DOOR_NXS_D;
            break;           
        }
      case DOOR_S:
        switch (-map[MAPPED][x][y])
        {
          case DOOR_S:        /* Don't overwrite */
          case DOOR_NS:
          case DOOR_SN:
          case DOOR_NXS:
          case DOOR_SXN:
          case DOOR_S_U:
          case DOOR_S_D:
          case DOOR_S_UD:
          case DOOR_NS_U:
          case DOOR_NS_D:
          case DOOR_NS_UD:
          case DOOR_NXS_U:
          case DOOR_NXS_D:
          case DOOR_NXS_UD:
            return;
          case DOOR_N_U:
            if (door == DOOR_S || door == DOOR_SXN)
              door = DOOR_NXS_U;
            else 
              door = DOOR_NS_U;
            break;
          case DOOR_N_D:
            if (door == DOOR_S || door == DOOR_SXN)
              door = DOOR_NXS_D;
            else 
              door = DOOR_NS_D;
            break;
          case DOOR_N_UD:
            if (door == DOOR_S || door == DOOR_SXN)
              door = DOOR_NXS_UD;
            else 
              door = DOOR_NS_UD;
            break;
          case DOOR_U:
            if (door == DOOR_S)
              door = DOOR_S_U;
            else if (door == DOOR_NS)
              door = DOOR_NS_U;
            else if (door == DOOR_NXS)
              door = DOOR_NXS_U;
            break;             
          case DOOR_D:
            if (door == DOOR_S)
              door = DOOR_S_D;
            else if (door == DOOR_NS)
              door = DOOR_NS_D;
            else if (door == DOOR_NXS)
              door = DOOR_NXS_D;
            break;           
        }      
        break;
        
      case DOOR_NW:
        if (-map[MAPPED][x][y] == DOOR_NESW)
          door = DOOR_CROSS;
       break;
      case DOOR_NE:
        if (-map[MAPPED][x][y] == DOOR_NWSE)
          door = DOOR_CROSS;
        break;   
        
      default:
        break;
    }
  }
  
  map[MAPPED][x][y] = -door_marks[door];
  
  return; 
}

/* MapDoor()
 * 1.) Check if dest room reverse exit matches this one
 * 2.) Print appropriate exit type to map
 */
static int MapDoor(int level, room_rnum room, sh_int door, struct room_direction_data *pexit, int x, int y)
{
  room_rnum prospect_room, revdoor_room = NOWHERE, a_room = NOWHERE; 
  int a, a_revdoor_room;

  prospect_room = pexit->to_room;
  
  /* Flag this exit as mapped so we don't map it again later */
  roomlist[level].exits[door][MAPPED] = TRUE;
  roomlist[level].exits[door][ROOM]   = prospect_room; 
  
  if (world[prospect_room].dir_option[rev_dir[door]])
    revdoor_room = world[prospect_room].dir_option[rev_dir[door]]->to_room;
  
  for (a=0; a < roomlist_top; a++) {
    if ( (roomlist[a].x_offset == x+offsets[door][0]) && 
         (roomlist[a].y_offset == y+offsets[door][1]) ) {
      a_room = roomlist[a].room;
      a_revdoor_room = roomlist[a].exits[rev_dir[door]][ROOM];
    }
  }
  
  /* ROOM VARS:
   * room           = this room
   * prospect_room  = room on the other side of this exit
   * revdoor_room   = room on the other side of the opposite exit in the 
   *                  destination room (normal this room -- room)
   * a_room         = room with these coordinates in the roomlist we're mapping
   * a_revdoor_room = room on the reverse of the opposite door at these coordinates in the roomlist
   * 
   * OTHERS:
   * level          = current level in roomlist
   * a              = level of a_room in roomlist
   * door           = direction of exit we're mapping
   * pexit          = pointer to potential destination room
   * 
   * x & y are the coordinates of this room.
   *        add offset[][] for the destination room location
   *        add door_offset[][] for the door to the destination room  */
 
  if (a_room == NOWHERE) {
    /* Space is unoccupied on the map...
     * Check if the room this connects to has reverse exit to this room
     *  If so, map a connecting exit
     *  If not, map a single exit */
    if (revdoor_room == room)
      add_door(x+door_offsets[door][0], y+door_offsets[door][1], door+DOORS_CONNECTED);
    else
      add_door(x+door_offsets[door][0], y+door_offsets[door][1], door);    
  } 
  else {
    /* Space is occupied on map already...
     * Check if a_room is the same room as prospect_room (the one we're trying to map)
     *  If so, does the reverse exit match this room?
     *    If so, map and connect
     *    If not, check if the reverse exit exists to another room
     *      if so, map each with non-connecting exits.
     *      If not, map a one-way to the propsect room.
     *  If not, does a_room have an exit towards this direction?
     *    If so, map non-connecting exits
     *    If not, map an exit on this side, with none on the other. */
    if (prospect_room == a_room) {
      if (revdoor_room == room)
        add_door(x+door_offsets[door][0], y+door_offsets[door][1], door+DOORS_CONNECTED);
      else if (revdoor_room != NOWHERE)
        add_door(x+door_offsets[door][0], y+door_offsets[door][1], door+DOORS_NOT_CONNECTED);    
      else
        add_door(x+door_offsets[door][0], y+door_offsets[door][1], door);
    }
    else if (a_revdoor_room != NOWHERE) 
      add_door(x+door_offsets[door][0], y+door_offsets[door][1], door+DOORS_NOT_CONNECTED);
    else 
      add_door(x+door_offsets[door][0], y+door_offsets[door][1], door);    
  }
  
  return 0; // Success
}

/* MapRoom()
 * 1.) Checks roomlist to make sure we've not exceeded our mapping capacity
 * 2.) Add the room to the map as long as one of the following is met:
 *      a.) The room is in the zone   OR
 *      b.) The room is the room the mapper is in   OR
 *      c.) The room is not in the zone and the spot on the map is empty. */
static int MapRoom(struct char_data *ch, room_rnum room, int x, int y)
{
  int a, b;
  
  for (a=0; roomlist[a].room; a++) {
    if (roomlist[a].room == room)
      return 1;   /* This room exists already */
    if (roomlist[a].x_offset == x) {
      if (roomlist[a].y_offset == y)
        return 2; /* There is something on this spot of the map already */
    }
  } 
  
  /* Abort further mapping if we've reached the top of the roomlist */
  if (a >= roomlist_top - 1 || roomlist[roomlist_top].room != NOWHERE) {
    mudlog(NRM, LVL_IMPL, TRUE, "MapArea: roomlist not big enough to map entire area. (asciimap.c)");
    return -1; 
  }

  /* We've probably already mapped the room in which ch is standing, so
   * this is just included for future expansion. */
  if (room == IN_ROOM(ch)) 
    map[MAPPED][x][y] = SECT_OFFSET(SECT_HERE);
  else if (GET_ROOM_ZONE(room) != GET_ROOM_ZONE(IN_ROOM(ch))) 
    map[MAPPED][x][y] = SECT_OFFSET(SECT(room)) + OUT_OF_ZONE;
  else
    map[MAPPED][x][y] = SECT_OFFSET(SECT(room));
  
  map[ROOM][x][y]       = room;
  roomlist[a].room      = room;  
  roomlist[a].x_offset  = x;
  roomlist[a].y_offset  = y;
  for (b=0; b<NUM_OF_DIRS; b++) {
    roomlist[a].exits[b][MAPPED]  = FALSE;
    roomlist[a].exits[b][ROOM]    = NOWHERE;
  }
  
  return 0; // Success!
}

/* ExploreRoom() - Checks all doors of a room and 
 *  1.) Adds immediate rooms to the map
 *  2.) Updates the exit mapping status in the roomlist structure */
static void ExploreRoom(struct char_data *ch, int level, room_rnum room, int *x, int *y, bool worldmap) 
{
  struct room_direction_data *pexit;
  sh_int door;
  
  for (door = 0; door < NUM_OF_DIRS; door++) {
   
    if (roomlist[level].exits[door][MAPPED])
      continue; /* Already mapped this exit */
    
    if ( (pexit = world[room].dir_option[door]) != NULL  &&
         (pexit->to_room > 0 ) && (pexit->to_room != NOWHERE) &&
         (!IS_SET(pexit->exit_info, EX_CLOSED)) &&
         (!IS_SET(pexit->exit_info, EX_HIDDEN)  || 
          PRF_FLAGGED(ch, PRF_HOLYLIGHT)      )   ) { 
 
      switch(door) {
        case NORTH:
        case SOUTH:
        case EAST:
        case WEST:
        case NORTHEAST:
        case NORTHWEST:
        case SOUTHEAST:
        case SOUTHWEST:    
          if (worldmap) {
            if (MapRoom(ch, pexit->to_room, *x+offsets_worldmap[door][0], *y+offsets_worldmap[door][1]) < 0)
              return;
          } else
            if (MapRoom(ch, pexit->to_room, *x+offsets[door][0], *y+offsets[door][1]) < 0)
              return;
        /* Map doors to these exits, but don't map the rooms themselves */
        case UP:
        case DOWN:
          if (!worldmap)
            MapDoor(level, room, door, pexit, *x, *y);
          roomlist[level].exits[door][ROOM] = pexit->to_room;
        default:
          break;
      }
    }
    else {
      roomlist[level].exits[door][ROOM] = NOWHERE;
    }
    roomlist[level].exits[door][MAPPED] = TRUE;
  }
  return;
}

/* Multipliers for the array size to allocate - worldmap has a lot more data */
#define ROOMLIST_NORMAL     5
#define ROOMLIST_WORLDMAP   10
/* MapArea() - Where the maping process starts and finishes
 *  Args:
 *  room       = This is here for str_and_map(), although it is not needed
 *  ch         = char pointer
 *  x          = current x position
 *  y          = current y position
 *  min        = bottom of map area (within canvas)
 *  max        = top of map area (within canvas)
 *  world      = true/false -> mapping in string form or world form
*/
static void MapArea(room_rnum room, struct char_data *ch, int x, int y, int min, int max, bool worldmap)
{
  int a=0, level = 0;
 
  if (!createroomlist(max*(worldmap ? ROOMLIST_WORLDMAP : ROOMLIST_NORMAL))) {
    mudlog(BRF, LVL_IMPL, TRUE, "SYSERR: ascimap.c - MapArea() unable to allocate memory for roomlist."); 
    send_to_char(ch, "Unable to map.\r\n");
    return;
  }
  
  roomlist[a].room = room;
  roomlist[a].x_offset = x;
  roomlist[a].y_offset = y;
  
  if(room == IN_ROOM(ch)) {
    map[MAPPED][x][y] = SECT_OFFSET(SECT_HERE);
    map[ROOM][x][y] = room;
  }
 
  /* Explore the room, map the connected rooms, then continue down the list
   * with newly added rooms in order -- results in a circular pattern starting
   * in the center and radiating outward to the defined map limits. */

  ExploreRoom(ch, level, roomlist[a].room, &x, &y, worldmap);
    
  for ( a=0; (a < roomlist_top && roomlist[a].room); a++ ) {
    x = roomlist[a].x_offset;
    y = roomlist[a].y_offset;
    if ( (x < min) || ( y < min) || ( x > max ) || ( y > max) )
      continue; 
    else
      ExploreRoom(ch, a, roomlist[a].room, &x, &y, worldmap);
  }

  createroomlist(0); /* free memory */
  return;
} 

#define IMM_FORMAT    "%s\t<send \"goto %d\" hint=\"[%d] %s\">%s\t</send>\tn%s"
#define MORTAL_FORMAT "%s%s%s"
/* Returns a string representation of the map */
static char *StringMap(int centre, int size, sh_int level)
{
  static char strmap[MAX_MAP*MAX_MAP*11 + MAX_MAP*2 + 1];
  char tmp_buf[100], *mp = strmap, *tmp;
  int x, y, sector;
  
  /* every row */
  for (x = centre - CANVAS_HEIGHT/2; x <= centre + CANVAS_HEIGHT/2; x++) {
    /* every column */
    for (y = centre - CANVAS_WIDTH/6; y <= centre + CANVAS_WIDTH/6; y++) {
      if (abs(centre - x)<=size && abs(centre-y)<=size) {
        if (map[MAPPED][x][y]<=0)
          tmp = door_info[abs(map[MAPPED][x][y])].disp; 
        else {      
          sector = SECT_OFFSET(map[MAPPED][x][y]); /* Decode the sector */
          if (sector == SECT_EMPTY)
            sprintf(tmp_buf, " %s ", map_info[sector].disp);
          else if (sector >= OUT_OF_ZONE) {
            if (level >= LVL_IMMORT)
              sprintf(tmp_buf, IMM_FORMAT,
                      OUTZONE_BEGIN, GET_ROOM_VNUM(map[ROOM][x][y]), 
                      GET_ROOM_VNUM(map[ROOM][x][y]), world[map[ROOM][x][y]].name,
                      map_info[sector - OUT_OF_ZONE].disp, OUTZONE_END);
            else
              sprintf(tmp_buf, MORTAL_FORMAT,
                      OUTZONE_BEGIN, map_info[sector - OUT_OF_ZONE].disp, 
                      OUTZONE_END);
          }
          else {
            if (level >= LVL_IMMORT)
              sprintf(tmp_buf, IMM_FORMAT, 
                      INZONE_BEGIN, GET_ROOM_VNUM(map[ROOM][x][y]), 
                      GET_ROOM_VNUM(map[ROOM][x][y]), world[map[ROOM][x][y]].name, 
                      map_info[sector].disp, INZONE_END);
            else
              sprintf(tmp_buf, MORTAL_FORMAT, 
                      INZONE_BEGIN, map_info[sector].disp, INZONE_END);
          }            
          tmp = tmp_buf; 
        }
      }
      else
        tmp = map_info[SECT_EMPTY].disp;
      strcpy(mp, tmp);
      mp += strlen(tmp);
    }
    strcpy(mp, "\r\n");
    mp+=2;
  }
  *mp='\0';
  return strmap;
}
#undef IMM_FORMAT 
#undef MORTAL_FORMAT
#define IMM_FORMAT    "\t<send \"goto %d\">%s\t</send>\tn"
#define MORTAL_FORMAT "%s"
/* Due to the volume of data in the world map, including the room number 
 * and name will cause the output buffer to flood. So, we have to forego
 * the fancy baloon tip on the worldmap. */
static char *WorldMap(int centre, int size, int mapshape, int maptype, sh_int level)
{
  static char strmap[MAX_MAP*MAX_MAP*5]; //4 + MAX_MAP*2 + 1];
  char *mp = strmap;
  int x, y, sector;;
  int xmin, xmax, ymin, ymax;
  char tmp_buf[100];

  switch(maptype) {
    case MAP_COMPACT:
      xmin = centre - size;
      xmax = centre + size;
      ymin = centre - 2*size;
      ymax = centre + 2*size;
      break;
    default:
      xmin = centre - CANVAS_HEIGHT/2;
      xmax = centre + CANVAS_HEIGHT/2;
      ymin = centre - CANVAS_WIDTH/2;
      ymax = centre + CANVAS_WIDTH/2;
  }
  
  /* every row */
  for (x = xmin; x <= xmax; x++) {
    /* every column */
    for (y = ymin ; y <= ymax ; y++) {
      if((mapshape == MAP_RECTANGLE && abs(centre - y) <= size*2  && abs(centre - x) <= size ) ||
          ((mapshape == MAP_CIRCLE) && (centre-x)*(centre-x) + (centre-y)*(centre-y)/4 <= (size * size + 1))) {
        sector = SECT_OFFSET(map[MAPPED][x][y]);
        if (sector == SECT_EMPTY || map[ROOM][x][y] == NOWHERE)
          sprintf(tmp_buf, "%s", map_info[SECT_EMPTY].disp);
        else if (sector >= OUT_OF_ZONE) {
          if (level >= LVL_IMMORT) 
            sprintf(tmp_buf, IMM_FORMAT,
                    GET_ROOM_VNUM(map[ROOM][x][y]), 
                    map_info[sector - OUT_OF_ZONE].disp);
          else 
            sprintf(tmp_buf, MORTAL_FORMAT,  
                    map_info[sector - OUT_OF_ZONE].disp);
        }
        else {
          if (level >= LVL_IMMORT)
            sprintf(tmp_buf, IMM_FORMAT, 
                    GET_ROOM_VNUM(map[ROOM][x][y]),
                    map_info[sector].disp);
          else 
            sprintf(tmp_buf, MORTAL_FORMAT,  map_info[sector].disp);
        }
          strcpy(mp, tmp_buf);
          mp += strlen(tmp_buf);
      } 
      else 
        strcpy(mp++, " ");
    }
    strcpy(mp, "\tn\r\n");
    mp+=4;
  }
  *mp='\0';
  return strmap;
}

static char *CompactStringMap(int centre, int size)
{
  static char strmap[MAX_MAP*MAX_MAP*12 + MAX_MAP*2 + 1];
  char *mp = strmap;
  int x, y;
  int sector;

  char tmp_buf[20]; // c strings are the worst -Doogie

  /* every row */
  for (x = centre - size; x <= centre + size; x++) {
    /* every column */
    for (y = centre - size; y <= centre + size; y++) {
      if (map[MAPPED][x][y]<=0)
          sprintf(tmp_buf, "%s", door_info[abs(map[MAPPED][x][y])].alt);
      else {
        sector = SECT_OFFSET(map[MAPPED][x][y]);
        if (sector == SECT_EMPTY)
          sprintf(tmp_buf, " %s ", map_info[sector].disp);
        else if (sector >= OUT_OF_ZONE)
          sprintf(tmp_buf, "%s%s%s", OUTZONE_BEGIN,
                  map_info[sector - OUT_OF_ZONE].disp, OUTZONE_END);
        else
          sprintf(tmp_buf, "%s%s%s", INZONE_BEGIN, 
                  map_info[sector].disp, INZONE_END);
      }
      strcpy(mp, tmp_buf);
      mp += strlen(tmp_buf);
    }
    strcpy(mp, "\r\n");
    mp+=2;
  }
  *mp='\0';
  return strmap;
}

/* Display a nicely formatted map with a legend */
static void perform_map( struct char_data *ch, char *argument, bool worldmap )
{
  int count = 0, size = DEFAULT_MAP_SIZE, mapshape = MAP_CIRCLE;
  int centre, x, y, min, max;
  char arg1[MAX_INPUT_LENGTH], arg2[MAX_INPUT_LENGTH], buf[MAX_STRING_LENGTH], buf1[MAX_STRING_LENGTH], buf2[MAX_STRING_LENGTH];

  two_arguments( argument, arg1 , arg2 );
  if (*arg1)
  {
    for (count=0;count<strlen(arg1); count++) {
      if (!isdigit(arg1[count])) {
        if (is_abbrev(arg1, "normal")) {
          worldmap=FALSE;
          count = -1;
          break;
        }
        else if (is_abbrev(arg1, "world")) {
          worldmap=TRUE;
          count = -1;
          break;
        }
        else {
          send_to_char(ch, "Usage: \tymap <normal|world> <distance> \tn\r\n");
          return;
        }
      }
    }
    if (count > 0) {
      size = atoi(arg1);
      count = 0;
    }
  }
  
  if (*arg2 && count != 0)
  { 
    size = atoi(arg2);
  }
  
  count = 0;
  
  if (size<0) {
    size = -size;
    mapshape = MAP_RECTANGLE;
  }
  size = URANGE(1,size,MAX_MAP_SIZE);

  centre = MAX_MAP/2;

  if (worldmap) {
    min = centre - 2*size;
    max = centre + 2*size;
  } else {
    min = centre - size;
    max = centre + size;
  }

  /* Blank the map */
  for (x = 0; x < MAX_MAP; ++x)
      for (y = 0; y < MAX_MAP; ++y) {
           map[MAPPED][x][y] = (!(y%2) && !worldmap) ? -DOOR_NONE : SECT_OFFSET(SECT_EMPTY);
           map[ROOM][x][y]   = NOWHERE;
      }
  /* Map the area, starting at the center -> current room */    
  MapArea(IN_ROOM(ch), ch, centre, centre, min, max, worldmap);

  /* Feel free to put your own MUD name or header in here */
  send_to_char(ch, " \tD- \tYtbaMUD Map System\tD %*.*s\tn\r\n"
                   "\tD .,-.__--.,-.__.-.-.\tn \tc%*s\r\n", CANVAS_WIDTH, CANVAS_WIDTH, 
                   "-------------------------------------------------------------------------------------------",
                   ((CANVAS_WIDTH + strlen(zone_table[GET_ROOM_ZONE(IN_ROOM(ch))].name)) / 2) + 1, 
                   zone_table[GET_ROOM_ZONE(IN_ROOM(ch))].name);
  /* A lot of extra \tn to get the spacing right */
  if (!worldmap) {
    count += sprintf(buf + count, "\tn\tD\t_Not Connected\tn\\\\");
    count += sprintf(buf + count, "\tn%s N \tn \tn \tn %s \tn S\\\\", door_info[DOOR_N].disp, door_info[DOOR_S].disp);
    count += sprintf(buf + count, "\tn %s E \tn \tn \tn %s \tn W\\\\", door_info[DOOR_E].disp, door_info[DOOR_W].disp);
    count += sprintf(buf + count, "\tn%s N&S %s E&W\\\\", door_info[DOOR_NXS].disp, door_info[DOOR_EXW].disp);
    count += sprintf(buf + count, "\tn%s NE \tn \tn %s \tn  SW\\\\", door_info[DOOR_NE].disp, door_info[DOOR_SW].disp);
    count += sprintf(buf + count, "\tn%s NW \tn \tn %s \tn  SE\\\\", door_info[DOOR_NW].disp, door_info[DOOR_SE].disp);
    count += sprintf(buf + count, "\tn %s Up \tn \tn %s \tn Down\\\\", door_info[DOOR_U].disp, door_info[DOOR_D].disp);
    count += sprintf(buf + count, "\tn\tD\t_Connected\tn\\\\");
    count += sprintf(buf + count, "\tn%s N-S \tn%s E-W\\\\", door_info[DOOR_NS].disp,door_info[DOOR_EW].disp);
  }
  count += sprintf(buf + count, "\tn\tD\t_Sector Type\tn\\\\");
  count += sprintf(buf + count, "\tn %s You\\\\", map_info[SECT_HERE].disp);
  count += sprintf(buf + count, "\tn %s Inside\\\\", map_info[SECT_INSIDE].disp);
  count += sprintf(buf + count, "\tn %s City\\\\", map_info[SECT_CITY].disp);
  count += sprintf(buf + count, "\tn %s Plains\\\\", map_info[SECT_FIELD].disp);
  count += sprintf(buf + count, "\tn %s Forest\\\\", map_info[SECT_FOREST].disp);
  count += sprintf(buf + count, "\tn %s Hills\\\\", map_info[SECT_HILLS].disp);
  count += sprintf(buf + count, "\tn %s Mountain\\\\", map_info[SECT_MOUNTAIN].disp);
  count += sprintf(buf + count, "\tn %s Swim\\\\", map_info[SECT_WATER_SWIM].disp);
  count += sprintf(buf + count, "\tn %s Boat\\\\", map_info[SECT_WATER_NOSWIM].disp);
  count += sprintf(buf + count, "\tn %s Underwater\\\\", map_info[SECT_UNDERWATER].disp);
  sprintf(buf + count, "\tn %s Flying\\\\", map_info[SECT_FLYING].disp);
  strcpy(buf, strfrmt(buf, LEGEND_WIDTH, CANVAS_HEIGHT + 2, FALSE, TRUE, TRUE));

  /* Start with an empty column */
  strcpy(buf1, strfrmt("",0, CANVAS_HEIGHT + 2, FALSE, FALSE, TRUE));

  /* Paste the legend */
  strcpy(buf2, strpaste(buf1, buf, "\tD | \tn"));

  /* Set up the map */
  memset(buf, ' ', CANVAS_WIDTH);
  count = (CANVAS_WIDTH);
  if(worldmap)
    count += sprintf(buf + count , "\r\n%s", WorldMap(centre, size, mapshape, MAP_NORMAL, GET_LEVEL(ch)));
  else
    count += sprintf(buf + count , "\r\n%s", StringMap(centre, size, GET_LEVEL(ch)));
  memset(buf + count, ' ', CANVAS_WIDTH);
  strcpy(buf + count + CANVAS_WIDTH, "\r\n");
  /* Paste it on */
  strcpy(buf2, strpaste(buf2, buf, "\tD | \tn"));
  /* Paste on the right border */
  strcpy(buf2, strpaste(buf2, buf1, "  "));
  /* Print it all out */
  send_to_char(ch, "%s", buf2);

  send_to_char(ch, "\tD `.-.__--.,-.__.-.-'\tn\r\n");
  return;
}

/* Display a string with the map beside it */
void str_and_map(char *str, struct char_data *ch, room_vnum target_room ) {
  int size, centre, x, y, min, max, char_size;
  bool worldmap;

  /* Check MUDs map config options - if disabled, just show room decsription */
  if (!can_see_map(ch)) {
    send_to_char(ch, "%s", strfrmt(str, GET_SCREEN_WIDTH(ch), 1, FALSE, FALSE, FALSE));
    return;
  }

  worldmap = show_worldmap(ch);

  if(!PRF_FLAGGED(ch, PRF_AUTOMAP)) {
    send_to_char(ch, "%s", strfrmt(str, GET_SCREEN_WIDTH(ch), 1, FALSE, FALSE, FALSE));
    return;
  }

  size = CONFIG_MINIMAP_SIZE;
  centre = MAX_MAP/2;
  min = centre - 2*size;
  max = centre + 2*size;

  for (x = 0; x < MAX_MAP; ++x)
    for (y = 0; y < MAX_MAP; ++y)
      map[MAPPED][x][y]= (!(y%2) && !worldmap) ? -DOOR_NONE : SECT_OFFSET(SECT_EMPTY);

  /* starts the mapping with the center room */
  MapArea(target_room, ch, centre, centre, min, max, worldmap ); 
  map[MAPPED][centre][centre] = SECT_OFFSET(SECT_HERE);

  /* char_size = rooms + doors + padding */
  if(worldmap)
    char_size = size * 4 + 5;
  else
    char_size = 3*(size+1) + (size) + 4;

  if(worldmap)
    send_to_char(ch, "%s", strpaste(strfrmt(str, GET_SCREEN_WIDTH(ch) - char_size, size*2 + 1, FALSE, TRUE, TRUE), WorldMap(centre, size, MAP_CIRCLE, MAP_COMPACT, GET_LEVEL(ch)), " \tn"));
  else
    send_to_char(ch, "%s", strpaste(strfrmt(str, GET_SCREEN_WIDTH(ch) - char_size, size*2 + 1, FALSE, TRUE, TRUE), CompactStringMap(centre, size), " \tn"));

}

static bool show_worldmap(struct char_data *ch) {
  room_rnum rm = IN_ROOM(ch);
  zone_rnum zn = GET_ROOM_ZONE(rm);

  if (ROOM_FLAGGED(rm, ROOM_WORLDMAP)) return TRUE;
  if (ZONE_FLAGGED(zn, ZONE_WORLDMAP)) return TRUE;

  return FALSE;
}

ACMD(do_map) {
  if (!can_see_map(ch)) {
    send_to_char(ch, "Sorry, the map is disabled!\r\n");
    return;
  }
  if (IS_DARK(IN_ROOM(ch)) && !CAN_SEE_IN_DARK(ch)) {
    send_to_char(ch, "It is too dark to see the map.\r\n");
    return;
  } else if (AFF_FLAGGED(ch, AFF_BLIND) && GET_LEVEL(ch) < LVL_IMMORT) {
    send_to_char(ch, "You can't see the map while blind!\r\n");
    return;
  }
  perform_map(ch, argument, show_worldmap(ch));
}
