/*
* $Id: map.c,v 1.2 2005/06/22 15:08:03 av1-op Exp $
*
* Author: Markus Stenberg <fingon@iki.fi>
*
* Last modified: Fri Dec 11 00:59:48 1998 fingon
*
* Original authors:
* 4.8.93- rdm created
* 6.16.93- jb modified, added hex_struct
* Since that modified by:
* '95 - '97: Markus Stenberg <fingon@iki.fi>
* '98 - '02: Thomas Wouters <thomas@xs4all.net>
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/file.h>
#include <time.h>
#include "mech.h"
#include "mech.events.h"
#include "create.h"
#include "glue.h"
#include "spath.h"
#include "autopilot.h"
#include "p.mech.maps.h"
#include "p.mechfile.h"
#include "p.mech.utils.h"
#include "p.mech.build.h"
#include "p.map.obj.h"
#include "p.mech.sensor.h"
#include "p.spath.h"
#include "p.debug.h"
void debug_fixmap(dbref player, void *data, char *buffer)
{
MAP *m = (MAP *) data;
int i, k;
MECH *mek;
if (!m)
return;
notify(player, tprintf("Checking %d entries..", m->first_free));
DOLIST(k, Contents(m->mynum)) {
if (Hardcode(k)) {
if (WhichSpecial(k) == GTYPE_MECH) {
MECH *mek;
/* Check if it's on the map */
for (i = 0; i < m->first_free; i++)
if (m->mechsOnMap[i] == k)
break;
if (i != m->first_free)
continue;
mek = getMech(k);
mek->mapindex = -1; /* Eep. */
mek->mapnumber = 0;
}
}
}
for (i = 0; i < m->first_free; i++)
if ((k = m->mechsOnMap[i]) >= 0) {
if (!IsMech(k)) {
notify(player,
tprintf
("Error: #%d isn't mech yet is in mapindex. Fixing..",
k));
m->mechsOnMap[i] = -1;
} else if (!(mek = getMech(k))) {
notify(player,
tprintf("Error: #%d has no mech data. Removing..", k));
m->mechsOnMap[i] = -1;
} else if (mek->mapindex != m->mynum) {
notify(player,
tprintf("Error: #%d isn't really here! Removing..",
k));
m->mechsOnMap[i] = -1;
} else if (mek->mapnumber != i) {
notify(player,
tprintf
("Error: #%d has invalid mapnumber (mn:%d <-> real:%d)..",
k, mek->mapnumber, i));
}
}
notify(player, "Done.");
}
/* Selectors */
#define SPECIAL_FREE 0
#define SPECIAL_ALLOC 1
/* Displays a map to player when they use the VIEW <X> <Y> command
* with a Map Object */
void map_view(dbref player, void *data, char *buffer)
{
MAP *mech_map = (MAP *) data;
int argc, i;
int x, y;
char *args[2];
int displayHeight = MAP_DISPLAY_HEIGHT, displayWidth = MAP_DISPLAY_WIDTH;
char *str;
char **maptext;
/* Check if its a valid map */
if (!mech_map)
return;
/* Make sure the proper number of arguments '<X> <Y>' were passed */
argc = mech_parseattributes(buffer, args, 2);
switch (argc) {
case 2:
x = BOUNDED(0, atoi(args[0]), mech_map->map_width - 1);
y = BOUNDED(0, atoi(args[1]), mech_map->map_height - 1);
break;
default:
notify(player, "Invalid number of parameters!");
return;
}
/* Get the Tacsize attribute from
* the player, if doesn't exist set the height and width to
* default params. If it does exist, check the values and
* make sure they are legit. */
str = silly_atr_get(player, A_TACSIZE);
if (!*str) {
displayHeight = MAP_DISPLAY_HEIGHT;
displayWidth = MAP_DISPLAY_WIDTH;
} else if (sscanf(str, "%d %d", &displayHeight, &displayWidth) != 2 ||
displayHeight > 24 || displayHeight < 5 || displayWidth < 5 ||
displayWidth > 40) {
notify(player,
"Illegal Tacsize attribute. Must be in format "
"'Height Width' . Height : 5-24 Width : 5-40");
displayHeight = MAP_DISPLAY_HEIGHT;
displayWidth = MAP_DISPLAY_WIDTH;
}
/* Everything worked but lets check the map size */
displayHeight = (displayHeight <= mech_map->map_height)
? displayHeight : mech_map->map_height;
displayWidth = (displayWidth <= mech_map->map_width)
? displayWidth : mech_map->map_width;
/* Get the map data */
maptext = MakeMapText(player, NULL, mech_map, x, y, displayWidth,
displayHeight, 3, 0);
/* Display the map to the player */
for (i = 0; maptext[i]; i++)
notify(player, maptext[i]);
}
void map_addhex(dbref player, void *data, char *buffer)
{
MAP *map;
int x, y, argc;
char *args[4], elev;
map = (MAP *) data;
if (!CheckData(player, map))
return;
argc = mech_parseattributes(buffer, args, 4);
DOCHECK(argc != 4, "Invalid number of arguments!");
x = atoi(args[0]);
y = atoi(args[1]);
elev = abs(atoi(args[3]));
DOCHECK(!((x >= 0) && (x < map->map_width) && (y >= 0) &&
(y < map->map_height)), "X,Y out of range!");
if (args[2][0] == '.')
SetTerrain(map, x, y, ' ');
else
SetTerrain(map, x, y, args[2][0]);
SetElevation(map, x, y, (elev <= MAX_ELEV) ? elev : MAX_ELEV);
notify(player, "Hex set!");
}
void map_mapemit(dbref player, void *data, char *buffer)
{
MAP *map;
map = (MAP *) data;
if (!CheckData(player, map))
return;
while (*buffer == ' ')
buffer++;
DOCHECK(!buffer || !*buffer, "What do you want to @mapemit?");
MapBroadcast(map, buffer);
notify(player, "Message sent!");
}
/* Logic: OPPOSITE sides must have water, within r<=3 of each other */
extern int dirs[6][2];
int water_distance(MAP * map, int x, int y, int dir, int max)
{
int i;
int x2, y2;
for (i = 1; i < max; i++) {
x = x + dirs[dir][0];
y = y + dirs[dir][1];
if (!x && dirs[dir][0])
y--;
x2 = BOUNDED(0, x, map->map_width - 1);
y2 = BOUNDED(0, y, map->map_height - 1);
if (x != x2 || y != y2)
return max;
if (GetTerrain(map, x, y) == WATER || GetTerrain(map, x, y) == ICE)
return i;
if (GetTerrain(map, x, y) != BRIDGE &&
GetTerrain(map, x, y) != ROAD)
return max;
}
return max;
}
static int eligible_bridge_hex(MAP * map, int x, int y)
{
int i, j, k;
for (k = 0; k < 3; k++)
if ((i = water_distance(map, x, y, k, 4)) < 4)
if ((j = water_distance(map, x, y, k + 3, 4)) < 4) {
if ((i - j) > 3)
continue;
return 1;
}
return 0;
}
/* Convert some of the roads to bridges */
static void make_bridges(MAP * map)
{
int x, y;
for (x = 0; x < map->map_width; x++)
for (y = 0; y < map->map_height; y++)
if (GetTerrain(map, x, y) == ROAD)
if (eligible_bridge_hex(map, x, y))
SetTerrainBase(map, x, y, BRIDGE);
}
int map_load(MAP * map, char * mapname)
{
char openfile[50];
char terr, elev;
int i1, i2, i3;
FILE *fp;
char row[MAPX * 2 + 3];
int i, j = 0, height, width, filemode;
if (strlen(mapname) >= MAP_NAME_SIZE)
mapname[MAP_NAME_SIZE] = 0;
sprintf(openfile, "%s/%s", MAP_PATH, mapname);
fp = my_open_file(openfile, "r", &filemode);
if (!fp) {
return -1;
}
del_mapobjs(map); /* Just in case */
if (map->map) {
for (i = 0; i < map->map_height; i++)
free((char *) (map->map[i]));
free((char *) (map->map));
}
if (fscanf(fp, "%d %d\n", &height, &width) != 2 || height < 1 ||
height > MAPY || width < 1 || width > MAPX) {
SendError(tprintf("Map #%d: Invalid height and/or width",
map->mynum));
width = DEFAULT_MAP_WIDTH;
height = DEFAULT_MAP_HEIGHT;
}
Create(map->map, unsigned char *, height);
for (i = 0; i < height; i++)
Create(map->map[i], unsigned char, width);
for (i = 0; i < height; i++) {
if (feof(fp)
|| fgets(row, 2 * MAPX + 2, fp) == NULL ||
strlen(row) < (2 * width)) {
break;
}
for (j = 0; j < width; j++) {
terr = row[2 * j];
elev = row[2 * j + 1] - '0';
switch (terr) {
case FIRE:
map->flags |= MAPFLAG_FIRES;
break;
case TFIRE:
case SMOKE:
case '.':
terr = GRASSLAND;
break;
case '\'':
terr = LIGHT_FOREST;
break;
}
if (!strcmp(GetTerrainName_base(terr), "Unknown")) {
SendError(tprintf("Map #%d: Invalid terrain at %d,%d: '%c'",
map->mynum, j, i, terr));
terr = GRASSLAND;
}
SetMap(map, j, i, terr, elev);
}
}
if (i != height) {
SendError(tprintf("Error: EOF reached prematurely. "
"(x%d != %d || y%d != %d)", j, width, i, height));
my_close_file(fp, &filemode);
return -2;
}
map->grav = 100;
map->temp = 20;
if (!feof(fp)) {
if (fscanf(fp, "%d: %d %d\n", &i1, &i2, &i3) == 3) {
map->flags = i1;
map->grav = i2;
map->temp = i3;
}
}
map->map_height = height;
map->map_width = width;
if (!MapNoBridgify(map))
make_bridges(map);
sprintf(map->mapname, mapname);
my_close_file(fp, &filemode);
return 0;
}
void map_loadmap(dbref player, void *data, char *buffer)
{
MAP *map;
char *args[1];
map = (MAP *) data;
if (!CheckData(player, map))
return;
DOCHECK(mech_parseattributes(buffer, args, 1) != 1,
"Invalid number of arguments!");
notify(player, tprintf("Loading %s", args[0]));
switch (map_load(map, args[0])) {
case -1:
notify(player, "Map not found.");
return;
case -2:
notify(player, "Map invalid.");
return;
case 0:
notify(player, "Map loaded.");
break;
default:
notify(player, "Unknown error while loading map!");
return;
}
if (player != 1) {
notify(player, "Clearing Mechs off Newly Loaded Map");
map_clearmechs(player, data, "");
}
}
mapobj *find_mapobj(MAP * map, int x, int y, int type);
void map_savemap(dbref player, void *data, char *buffer)
{
MAP *map;
char *args[1];
FILE *fp;
char openfile[50];
int i, j;
char row[MAPX * 2 + 1];
char terrain;
int filemode;
map = (MAP *) data;
if (!CheckData(player, map))
return;
DOCHECK(mech_parseattributes(buffer, args, 1) != 1,
"Invalid number of arguments!");
if (strlen(args[0]) >= MAP_NAME_SIZE)
args[MAP_NAME_SIZE] = 0;
notify(player, tprintf("Saving %s", args[0]));
sprintf(openfile, "%s/", MAP_PATH);
strcat(openfile, args[0]);
DOCHECK(!(fp =
my_open_file(openfile, "w", &filemode)),
"Unable to open the map file!");
fprintf(fp, "%d %d\n", map->map_height, map->map_width);
for (i = 0; i < map->map_height; i++) {
mapobj *mo;
row[0] = 0;
for (j = 0; j < map->map_width; j++) {
terrain = GetTerrain(map, j, i);
switch (terrain) {
case ' ':
terrain = '.';
break;
case FIRE:
/* check if we're burnin', if so, alter terrain type */
if ((mo = find_mapobj(map, j, i, TYPE_FIRE)))
terrain = TFIRE;
else if (!(map->flags & MAPFLAG_FIRES)) {
SetTerrain(map, j, i, ' ');
SendEvent(tprintf
("[lost?] fire event noticed on map #%d (%s) at %d,%d",
map->mynum, map->mapname, j, i));
terrain = '.';
}
break;
case SMOKE:
terrain = GetRTerrain(map, j, i);
if (terrain == ' ')
terrain = '.';
if (terrain == SMOKE) {
SetTerrain(map, j, i, ' ');
SendEvent(tprintf
("[lost?] smoke event noticed on map #%d (%s) at %d,%d",
map->mynum, map->mapname, j, i));
terrain = '.';
}
break;
}
row[j * 2] = terrain;
row[j * 2 + 1] = GetElevation(map, j, i) + '0';
}
row[j * 2] = 0;
fprintf(fp, "%s\n", row);
}
if ((i = (map->flags & ~(MAPFLAG_MAPO))))
fprintf(fp, "%d: %d %d\n", i, map->grav, map->temp);
notify(player, "Saving complete!");
my_close_file(fp, &filemode);
}
void map_setmapsize(dbref player, void *data, char *buffer)
{
MAP *oldmap;
unsigned char **map;
int x, y, i, j, failed = 0, argc, x1, y1;
char *args[4];
oldmap = (MAP *) data;
if (!CheckData(player, oldmap))
return;
DOCHECK(oldmap->mapobj[TYPE_BITS],
"Invalid map for size change, sorry.");
DOCHECK((argc =
mech_parseattributes(buffer, args, 4)) != 2,
"Invalid number of arguments (X/Y expected)");
x = atoi(args[0]);
y = atoi(args[1]);
DOCHECK(!((x >= 0) && (x <= MAPX) && (y >= 0) &&
(y <= MAPY)), "X,Y out of range!");
/* allocate new map space */
Create(map, unsigned char *, y);
for (i = 0; i < y; i++)
Create(map[i], unsigned char, x);
if (failed)
SendError("Memory allocation failed in setmapsize!");
else {
/* Initialize the hexes in the new map to blank */
for (i = 0; i < y; i++)
for (j = 0; j < x; j++)
SetMapB(map, j, i, ' ', 0);
/* Copy old map into new map */
x1 = (oldmap->map_width < x) ? oldmap->map_width : x;
y1 = (oldmap->map_height < y) ? oldmap->map_height : y;
for (i = 0; i < y1; i++)
for (j = 0; j < x1; j++)
SetMapB(map, j, i, GetTerrain(oldmap, j, i),
GetElevation(oldmap, j, i));
/* Now free the old map */
for (i = oldmap->map_height - 1; i >= 0; i--)
free((char *) (oldmap->map[i]));
del_mapobjs(oldmap);
/* set new map size and pointer to new map space */
oldmap->map_height = y;
oldmap->map_width = x;
oldmap->map = map;
notify(player, "Size set.");
}
}
void map_clearmechs(dbref player, void *data, char *buffer)
{
MAP *map;
map = (MAP *) data;
if (CheckData(player, map))
ShutDownMap(player, map->mynum);
}
extern void update_LOSinfo(dbref, MAP *);
void map_update(dbref obj, void *data)
{
MAP *map = ((MAP *) data);
MECH *mech;
char *tmps, changemsg[LBUF_SIZE] = "";
int ma, ml, wind, wspeed, cloudbase = 200;
int oldl, oldv, i, j;
AUTO *au;
if (!(muxevent_tick % 60)) {
oldl = map->maplight;
oldv = map->mapvis;
if (!(tmps = silly_atr_get(obj, A_MAPVIS)) ||
sscanf(tmps, "%d %d %d %d %d %[^\n]", &ma, &ml, &wind,
&wspeed, &cloudbase, changemsg) < 4) {
ma = 30;
ml = 2;
wind = 0;
wspeed = 0;
cloudbase = 200;
}
map->winddir = wind;
map->windspeed = wspeed;
map->mapvis = BOUNDED(0, ma, 60);
map->maxvis = BOUNDED(24, ma * 3, 60);
map->maplight = BOUNDED(0, ml, 2);
map->cloudbase = cloudbase;
if (ml != oldl || ma != oldv)
for (i = 0; i < map->first_free; i++) {
if ((j = map->mechsOnMap[i]) < 0)
continue;
if (!(mech = getMech(j)))
continue;
if (ml != oldl)
sensor_light_availability_check(mech);
if (strlen(changemsg) > 5)
mech_notify(mech, MECHALL, changemsg);
if (MechAuto(mech) > 0)
if ((au = FindObjectsData(MechAuto(mech))))
if (Gunning(au))
UpdateAutoSensor(au, 0);
}
}
if (map->moves) {
update_LOSinfo(obj, map);
map->moves = 0;
}
/* Fire/Smoke are event-driven -> nothing related to them done here */
}
void initialize_map_empty(MAP * new, dbref key)
{
int i, j;
new->mynum = key;
new->map_width = DEFAULT_MAP_WIDTH;
new->map_height = DEFAULT_MAP_HEIGHT;
Create(new->map, unsigned char *, new->map_height);
for (i = 0; i < new->map_height; i++)
Create(new->map[i], unsigned char, new->map_width);
for (i = 0; i < new->map_height; i++)
for (j = 0; j < new->map_width; j++)
SetMap(new, j, i, ' ', 0);
}
/* Mem alloc/free routines */
void newfreemap(dbref key, void **data, int selector)
{
MAP *new = *data;
int i;
switch (selector) {
case SPECIAL_ALLOC:
initialize_map_empty(new, key);
/* allocate default map space */
for (i = 0; i < NUM_MAPOBJTYPES; i++)
new->mapobj[i] = NULL;
sprintf(new->mapname, "%s", "Default Map");
break;
case SPECIAL_FREE:
del_mapobjs(new);
if (new->map) {
for (i = new->map_height - 1; i >= 0; i--)
if (new->map[i])
free((char *) (new->map[i]));
free((char *) (new->map));
}
break;
}
}
int map_sizefun(void *data, int flag)
{
MAP *map = (MAP *) data;
int size = 0;
if (!map)
return 0;
size = sizeof(*map);
if (map->map)
size += sizeof(*map->map);
return size;
}
void map_listmechs(dbref player, void *data, char *buffer)
{
MAP *map;
MECH *tempMech;
int i;
int count = 0;
char valid[50];
char *ID;
char *args[2];
char *cmds[] = { "MECHS", "OBJS", NULL };
enum {
MECHS, OBJS
};
map = (MAP *) data;
if (!CheckData(player, map))
return;
DOCHECK(mech_parseattributes(buffer, args, 1) == 0,
"Supply target type too!");
switch (listmatch(cmds, args[0])) {
case MECHS:
notify(player, "--- Mechs on Map ---");
for (i = 0; i < map->first_free; i++) {
if (map->mechsOnMap[i] != -1) {
tempMech = getMech(map->mechsOnMap[i]);
ID = MechIDS(tempMech, 0);
if (tempMech)
strcpy(valid, "Valid Data");
else
strcpy(valid,
"Invalid Object Data! Remove this Mech!");
notify(player, tprintf("Mech DB Number: %d : [%s]\t%s",
map->mechsOnMap[i], ID, valid));
count++;
}
}
notify(player, tprintf("%d Mechs On Map", count));
notify(player, tprintf("%d positions open",
MAX_MECHS_PER_MAP - count));
if (count != map->first_free)
notify(player,
tprintf("%d is first free slot, according to db.",
map->first_free));
return;
break;
case OBJS:
list_mapobjs(player, map);
return;
break;
}
notify(player, tprintf("Invalid argument (%s)!", args[0]));
return;
}
void clear_hex(MECH * mech, int x, int y, int meant)
{
MAP *map;
if (!(map = getMap(mech->mapindex)))
return;
switch (GetTerrain(map, x, y)) {
case HEAVY_FOREST:
SetTerrain(map, x, y, LIGHT_FOREST);
break;
case LIGHT_FOREST:
if (Number(1, 2) == 1)
SetTerrain(map, x, y, ROUGH);
else
SetTerrain(map, x, y, GRASSLAND);
break;
default:
return;
}
if (meant) {
MechLOSBroadcast(mech, tprintf("'s shot clears %d,%d!", x, y));
mech_notify(mech, MECHALL, tprintf("You clear %d,%d.", x, y));
} else {
MechLOSBroadcast(mech, tprintf("'s stray shot clears %d,%d!", x,
y));
mech_notify(mech, MECHALL,
tprintf("You accidentally clear the %d,%d!", x, y));
}
}
MAP *spath_map;
#define readval(f,t) DOCHECK(readint(f, t), "Invalid argument!")
void map_pathfind(dbref player, void *data, char *buffer)
{
MAP *map = (MAP *) data;
char *args[6];
int argc;
int x1, y1, x2, y2;
time_t start_t;
int errper = -1;
if (!map)
return;
argc = mech_parseattributes(buffer, args, 6);
DOCHECK((argc < 4 || argc > 5), "Invalid arguments!");
readval(x1, args[0]);
readval(y1, args[1]);
readval(x2, args[2]);
readval(y2, args[3]);
if (argc > 4) {
readval(errper, args[4]);
errper = BOUNDED(0, errper, 1000);
}
spath_map = map;
start_t = time(NULL);
}
#undef readval
void UpdateMechsTerrain(MAP * map, int x, int y, int t)
{
MECH *mech;
int i;
for (i = 0; i < map->first_free; i++) {
if (!(mech = FindObjectsData(map->mechsOnMap[i])))
continue;
if (MechX(mech) != x || MechY(mech) != y)
continue;
if (MechTerrain(mech) != t) {
MechTerrain(mech) = t;
MarkForLOSUpdate(mech);
}
}
}