/**************************************************************************
* 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 MAX_MAP_DIR 6
#define MAX_MAP_FOLLOW 4
#define SECT_EMPTY 30 /* anything greater than num sect types */
#define SECT_STRANGE (SECT_EMPTY + 1)
#define SECT_HERE (SECT_STRANGE + 1)
#define DOOR_NS -1
#define DOOR_EW -2
#define DOOR_UP -3
#define DOOR_DOWN -4
#define VDOOR_NS -5
#define VDOOR_EW -6
#define DOOR_NONE -7
#define NUM_DOOR_TYPES 7
#define MAP_CIRCLE 0
#define MAP_RECTANGLE 1
#define MAP_NORMAL 0
#define MAP_COMPACT 1
struct map_info_type
{
int sector_type;
char disp[20];
};
static struct map_info_type door_info[] =
{
{ DOOR_NONE, " " },
{ VDOOR_EW, " @m+@n " },
{ VDOOR_NS, " @m+@n "},
{ DOOR_DOWN, "@r-@n " },
{ DOOR_UP, "@r+@n " },
{ DOOR_EW, " - " },
{ DOOR_NS, " | " }
};
static struct map_info_type compact_door_info[] =
{
{ DOOR_NONE, " " },
{ VDOOR_EW, " @m+@n " },
{ VDOOR_NS, " @m+@n "},
{ DOOR_DOWN, "@r-@n" },
{ DOOR_UP, "@r+@n" },
{ DOOR_EW, "-" },
{ DOOR_NS, " | " }
};
/* Add new sector types below for both map_info and world_map_info */
/* The last 3 MUST remain the same, although the symbol can be changed */
/* New sectors also need to be added to the perform_map function below */
static struct map_info_type map_info[] =
{
{ SECT_INSIDE, "@c[@n.@c]@n" }, /* 0 */
{ SECT_CITY, "@c[@wC@c]@n" },
{ SECT_FIELD, "@c[@g,@c]@n" },
{ SECT_FOREST, "@c[@gY@c]@n" },
{ SECT_HILLS, "@c[@Mm@c]@n" },
{ SECT_MOUNTAIN, "@c[@rM@c]@n" }, /* 5 */
{ SECT_WATER_SWIM, "@c[@c~@c]@n" },
{ SECT_WATER_NOSWIM, "@c[@b=@c]@n" },
{ SECT_FLYING, "@c[@C^@c]@n" },
{ SECT_UNDERWATER, "@c[@bU@c]@n" },
{ -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_STRANGE, "@c[@R?@c]@n" },
{ SECT_HERE, "@c[@B!@c]@n" },
};
static struct map_info_type world_map_info[] =
{
{ SECT_INSIDE, "@n." }, /* 0 */
{ SECT_CITY, "@wC" },
{ SECT_FIELD, "@g," },
{ SECT_FOREST, "@gY" },
{ SECT_HILLS, "@Mm" },
{ SECT_MOUNTAIN, "@rM" }, /* 5 */
{ SECT_WATER_SWIM, "@c~" },
{ SECT_WATER_NOSWIM, "@b=" },
{ SECT_FLYING, "@C^" },
{ SECT_UNDERWATER, "@bU" },
{ -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_STRANGE, "@R?" },
{ SECT_HERE, "@B!" },
};
static int map[MAX_MAP][MAX_MAP];
static int offsets[4][2] ={ {-2, 0},{ 0, 2},{ 2, 0},{ 0, -2} };
static int offsets_worldmap[4][2] ={ {-1, 0},{ 0, 1},{ 1, 0},{ 0, -1} };
static int door_offsets[6][2] ={ {-1, 0},{ 0, 1},{ 1, 0},{ 0, -1},{ -1, 1},{ 1, 1} };
static int door_marks[6] = { DOOR_NS, DOOR_EW, DOOR_NS, DOOR_EW, DOOR_UP, DOOR_DOWN };
static int vdoor_marks[4] = { VDOOR_NS, VDOOR_EW, VDOOR_NS, VDOOR_EW };
/******************************************************************************
* End Local (File Scope) Defines and Global Variables
*****************************************************************************/
/******************************************************************************
* Begin Local (File Scope) Function Prototypes
*****************************************************************************/
static void MapArea(room_rnum room, struct char_data *ch, int x, int y, int min, int max, sh_int xpos, sh_int ypos, bool worldmap);
static char *StringMap(int centre, int size);
static char *WorldMap(int centre, int size, int mapshape, int maptype );
static char *CompactStringMap(int centre, int size);
static void perform_map( struct char_data *ch, char *argument, bool worldmap );
/******************************************************************************
* End Local (File Scope) Function Prototypes
*****************************************************************************/
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;
}
/* MapArea function - create the actual map */
static void MapArea(room_rnum room, struct char_data *ch, int x, int y, int min, int max, sh_int xpos, sh_int ypos, bool worldmap)
{
room_rnum prospect_room;
struct room_direction_data *pexit;
int door, ew_size=0, ns_size=0, x_exit_pos=0, y_exit_pos=0;
sh_int prospect_xpos, prospect_ypos;
if (map[x][y] < 0)
return; /* this is a door */
/* marks the room as visited */
if(room == IN_ROOM(ch))
map[x][y] = SECT_HERE;
else
map[x][y] = SECT(room);
if ( (x < min) || ( y < min) || ( x > max ) || ( y > max) ) return;
/* Check for exits */
for ( door = 0; door < MAX_MAP_DIR; door++ ) {
if( door < MAX_MAP_FOLLOW &&
xpos+door_offsets[door][0] >= 0 &&
xpos+door_offsets[door][0] <= ns_size &&
ypos+door_offsets[door][1] >= 0 &&
ypos+door_offsets[door][1] <= ew_size)
{ /* Virtual exit */
map[x+door_offsets[door][0]][y+door_offsets[door][1]] = vdoor_marks[door] ;
if (map[x+offsets[door][0]][y+offsets[door][1]] == SECT_EMPTY )
MapArea(room,ch,x + offsets[door][0], y + offsets[door][1], min, max, xpos+door_offsets[door][0], ypos+door_offsets[door][1], worldmap);
continue;
}
if ( (pexit = world[room].dir_option[door]) != NULL &&
(pexit->to_room > 0 ) && (pexit->to_room != NOWHERE) &&
(!IS_SET(pexit->exit_info, EX_CLOSED))) { /* A real exit */
/* But is the door here... */
switch (door) {
case NORTH:
if(xpos > 0 || ypos!=y_exit_pos) continue;
break;
case SOUTH:
if(xpos < ns_size || ypos!=y_exit_pos) continue;
break;
case EAST:
if(ypos < ew_size || xpos!=x_exit_pos) continue;
break;
case WEST:
if(ypos > 0 || xpos!=x_exit_pos) continue;
break;
}
/* if ( (x < min) || ( y < min) || ( x > max ) || ( y > max) ) return;*/
prospect_room = pexit->to_room;
/* one way into area OR maze */
if ( world[prospect_room].dir_option[rev_dir[door]] &&
world[prospect_room].dir_option[rev_dir[door]]->to_room != room) {
map[x][y] = SECT_STRANGE;
return;
}
if(!worldmap)
map[x+door_offsets[door][0]][y+door_offsets[door][1]] = door_marks[door] ;
prospect_xpos = prospect_ypos = 0;
switch (door) {
case NORTH:
prospect_xpos = ns_size;
case SOUTH:
prospect_ypos = world[prospect_room].dir_option[rev_dir[door]] ? y_exit_pos : ew_size/2;
break;
case WEST:
prospect_ypos = ew_size;
case EAST:
prospect_xpos = world[prospect_room].dir_option[rev_dir[door]] ? x_exit_pos : ns_size/2;
}
if(worldmap) {
if ( door < MAX_MAP_FOLLOW && map[x+offsets_worldmap[door][0]][y+offsets_worldmap[door][1]] == SECT_EMPTY )
MapArea(pexit->to_room,ch,x + offsets_worldmap[door][0], y + offsets_worldmap[door][1], min, max, prospect_xpos, prospect_ypos, worldmap);
} else {
if ( door < MAX_MAP_FOLLOW && map[x+offsets[door][0]][y+offsets[door][1]] == SECT_EMPTY )
MapArea(pexit->to_room,ch,x + offsets[door][0], y + offsets[door][1], min, max, prospect_xpos, prospect_ypos, worldmap);
}
} /* end if exit there */
}
return;
}
/* Returns a string representation of the map */
static char *StringMap(int centre, int size)
{
static char strmap[MAX_MAP*MAX_MAP*11 + MAX_MAP*2 + 1];
char *mp = strmap;
char *tmp;
int x, y;
/* 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)
tmp = (map[x][y]<0) ? \
door_info[NUM_DOOR_TYPES + map[x][y]].disp : \
map_info[map[x][y]].disp ;
else
tmp = map_info[SECT_EMPTY].disp;
strcpy(mp, tmp);
mp += strlen(tmp);
}
strcpy(mp, "\r\n");
mp+=2;
}
*mp='\0';
return strmap;
}
static char *WorldMap(int centre, int size, int mapshape, int maptype )
{
static char strmap[MAX_MAP*MAX_MAP*4 + MAX_MAP*2 + 1];
char *mp = strmap;
int x, y;
int xmin, xmax, ymin, ymax;
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 = centre - size; x <= centre + size; x++) { */
for (x = xmin; x <= xmax; x++) {
/* every column */
/* for (y = centre - (2*size) ; y <= centre + (2*size) ; y++) { */
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))) {
strcpy(mp, world_map_info[map[x][y]].disp);
mp += strlen(world_map_info[map[x][y]].disp);
} else {
strcpy(mp++, " ");
}
}
strcpy(mp, "@n\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;
/* every row */
for (x = centre - size; x <= centre + size; x++) {
/* every column */
for (y = centre - size; y <= centre + size; y++) {
strcpy(mp, (map[x][y]<0) ? \
compact_door_info[NUM_DOOR_TYPES + map[x][y]].disp : \
map_info[map[x][y]].disp);
mp += strlen((map[x][y]<0) ? \
compact_door_info[NUM_DOOR_TYPES + map[x][y]].disp : \
map_info[map[x][y]].disp);
}
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 size = DEFAULT_MAP_SIZE;
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];
int count = 0;
int ew_size=0, ns_size=0;
int mapshape = MAP_CIRCLE;
two_arguments( argument, arg1 , arg2 );
if(*arg1)
{
size = atoi(arg1);
}
if (*arg2)
{
if (is_abbrev(arg2, "normal")) worldmap=FALSE;
else if (is_abbrev(arg2, "world")) worldmap=TRUE;
else {
send_to_char(ch, "Usage: @ymap <distance> [ normal | world ]@n");
return;
}
}
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[x][y]= (!(y%2) && !worldmap) ? DOOR_NONE : SECT_EMPTY;
/* starts the mapping with the centre room */
MapArea(IN_ROOM(ch), ch, centre, centre, min, max, ns_size/2, ew_size/2, worldmap);
/* marks the center, where ch is */
map[centre][centre] = SECT_HERE;
/* Feel free to put your own MUD name or header in here */
send_to_char(ch, " @Y-@ytbaMUD Map System@Y-@n\r\n"
"@D .-.__--.,--.__.-.@n\r\n" );
count += sprintf(buf + count, "@n@n@n%s Up\\\\", door_info[NUM_DOOR_TYPES + DOOR_UP].disp);
count += sprintf(buf + count, "@n@n@n%s Down\\\\", door_info[NUM_DOOR_TYPES + DOOR_DOWN].disp);
count += sprintf(buf + count, "@n%s You\\\\", map_info[SECT_HERE].disp);
count += sprintf(buf + count, "@n%s Inside\\\\", map_info[SECT_INSIDE].disp);
count += sprintf(buf + count, "@n%s City\\\\", map_info[SECT_CITY].disp);
count += sprintf(buf + count, "@n%s Field\\\\", map_info[SECT_FIELD].disp);
count += sprintf(buf + count, "@n%s Forest\\\\", map_info[SECT_FOREST].disp);
count += sprintf(buf + count, "@n%s Hills\\\\", map_info[SECT_HILLS].disp);
count += sprintf(buf + count, "@n%s Mountain\\\\", map_info[SECT_MOUNTAIN].disp);
count += sprintf(buf + count, "@n%s Swim\\\\", map_info[SECT_WATER_SWIM].disp);
count += sprintf(buf + count, "@n%s Boat\\\\", map_info[SECT_WATER_NOSWIM].disp);
count += sprintf(buf + count, "@n%s Flying\\\\", map_info[SECT_FLYING].disp);
count += sprintf(buf + count, "@n%s Underwater\\\\", map_info[SECT_UNDERWATER].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, "@D | @n"));
/* 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));
else
count += sprintf(buf + count , "\r\n%s", StringMap(centre, size));
memset(buf + count, ' ', CANVAS_WIDTH);
strcpy(buf + count + CANVAS_WIDTH, "\r\n");
/* Paste it on */
strcpy(buf2, strpaste(buf2, buf, "@D | @n"));
/* Paste on the right border */
strcpy(buf2, strpaste(buf2, buf1, " "));
/* Print it all out */
send_to_char(ch, "%s", buf2);
send_to_char(ch, "@D `.-.__--.,-.__.-.-'@n\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;
int ew_size=0, ns_size=0;
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 = ROOM_FLAGGED(target_room, ROOM_WORLDMAP) ? TRUE : FALSE ;
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[x][y]= (!(y%2) && !worldmap) ? DOOR_NONE : SECT_EMPTY;
/* starts the mapping with the center room */
MapArea(target_room, ch, centre, centre, min, max, ns_size/2, ew_size/2, worldmap );
map[centre][centre] = 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), " "));
else
send_to_char(ch, "%s", strpaste(strfrmt(str, GET_SCREEN_WIDTH(ch) - char_size, size*2 + 1, FALSE, TRUE, TRUE), CompactStringMap(centre, size), " "));
}
ACMD(do_map) {
if (!can_see_map(ch)) {
send_to_char(ch, "Sorry, the map is disabled!\r\n");
return;
}
perform_map(ch, argument, ROOM_FLAGGED(IN_ROOM(ch), ROOM_WORLDMAP) ? 1 : 0 );
}