nexus/map.c

5326 lines
128 KiB
C

#include <assert.h>
#include <ctype.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "defs.h"
#include "flag.h"
#include "io.h"
#include "move.h"
#include "nexus.h"
#include "lf.h"
#include "map.h"
#include "objects.h"
#include "text.h"
#include "vault.h"
extern habitat_t *firsthabitat,*lasthabitat;
extern job_t *firstjob;
extern map_t *firstmap,*lastmap;
extern region_t *firstregion,*lastregion;
extern regionoutline_t *firstregionoutline,*lastregionoutline;
extern regiontype_t *firstregiontype,*lastregiontype;
extern celltype_t *firstcelltype, *lastcelltype;
extern objecttype_t *objecttype,*lastobjecttype;
extern race_t *firstrace;
extern int viewx,viewy,vieww,viewh;
extern lifeform_t *player;
extern lifeform_t *godlf[];
extern int ngodlfs;
extern glyph_t tempglyph;
extern enum OBCLASS sortorder[];
extern enum ERROR reason;
extern enum GAMEMODE gamemode;
extern int needredraw;
extern long curtime;
cell_t *addcell(map_t *m, int x, int y) {
cell_t *cell;
m->cell[(y*m->w)+x] = malloc(sizeof(cell_t));
cell = m->cell[(y*m->w)+x];
cell->map = m;
cell->x = x;
cell->y = y;
cell->habitat = m->habitat;
cell->obpile = addobpile(NOOWNER, cell, NOOB);
setcelltype(cell, cell->habitat->solidcelltype);
cell->lf = NULL;
cell->room = NULL;
cell->lit = L_NOTLIT;
cell->origlit = L_NOTLIT;
cell->littimer = 0;
cell->origlittimer = 0;
cell->writing = NULL;
cell->known = B_FALSE;
cell->knownglyph.ch = ' ';
cell->knownglyph.colour = C_GREY;
cell->visited = B_FALSE;
cell->filled = B_FALSE;
return cell;
}
habitat_t *addhabitat(enum HABITAT id, char *name, enum CELLTYPE emptycell, enum CELLTYPE solidcell, int thingchance, int obchance, int vaultchance, int maxvisrange) {
habitat_t *a;
// add to the end of the list
if (firsthabitat == NULL) {
firsthabitat = malloc(sizeof(habitat_t));
a = firsthabitat;
a->prev = NULL;
} else {
// go to end of list
a = lasthabitat;
a->next = malloc(sizeof(habitat_t));
a->next->prev = a;
a = a->next;
}
lasthabitat = a;
a->next = NULL;
// props
a->id = id;
a->name = strdup(name);
a->emptycelltype = emptycell;
a->solidcelltype = solidcell;
a->randthingpct = thingchance;
a->randobpct = obchance;
a->randvaultpct = vaultchance;
a->maxvisrange = maxvisrange;
return a;
}
void addhomeobs(lifeform_t *lf) {
flag_t *f;
for (f = lf->flags->first ; f ; f = f->next) {
if (f->id == F_HOMEOB) {
addob(lf->cell->obpile, f->text);
} else if (f->id == F_HOMELEVOB) {
int i,amt;
amt = rnd(f->val[0],f->val[1]);
for (i = 0; i < amt; i++) {
cell_t *c;
// pick new EMPTY random spot
c = getrandomcell(lf->cell->map);
while (!cellwalkable(NULL, c, NULL)) {
c = getrandomcell(lf->cell->map);
}
addob(c->obpile, f->text);
}
}
}
}
map_t *addmap(void) {
map_t *a;
int id;
int i;
// is there already a map?
if (lastmap) {
id = lastmap->id + 1;
} else {
id = 0;
}
// add to the end of the list
if (firstmap == NULL) {
firstmap = malloc(sizeof(map_t));
a = firstmap;
a->prev = NULL;
} else {
// go to end of list
a = lastmap;
a->next = malloc(sizeof(map_t));
a->next->prev = a;
a = a->next;
}
lastmap = a;
a->next = NULL;
// props
a->id = id;
a->lf = NULL;
for (i = 0; i < MAXDIR_ORTH; i++) {
a->nextmap[i] = -1;
}
a->flags = addflagpile(NULL, NULL);
a->beingcreated = B_TRUE;
a->habitat = findhabitat(H_DUNGEON); // default!!!
a->lastplayervisit = -1;
a->nfixedrooms = 0;
return a;
}
// when monsters are made during level generation, autogen will be true. otherwise false;
// if "rid" RR_NONE, paste racename to get the race.
// otherwise just use the given race.
lifeform_t *addmonster(cell_t *c, enum RACE rid, char *racename, int jobok, int amt, int autogen, int *nadded) {
lifeform_t *lf = NULL;
race_t *r;
int db = B_FALSE;
flagpile_t *wantflags = NULL;
enum JOB wantjob = J_NONE;
if (nadded) *nadded = 0;
// ie. don't create mosnters on closed doors!
if (!cellwalkable(NULL, c, NULL)) {
return NULL;
}
if (rid != R_SPECIFIED) {
if (rid == R_RANDOM) {
r = getrandomrace(c, NA);
} else {
r = findrace(rid);
}
} else {
// get params
wantflags = addflagpile(NULL, NULL);
rid = parserace(racename, wantflags, &wantjob);
if (rid == R_RANDOM) {
r = getrandomrace(c, NA);
} else {
r = findrace(rid);
}
}
if (!r) {
return NULL;
}
if (db) {
char buf[BUFLEN];
snprintf(buf, BUFLEN, "addmonster for '%s'->%s",racename, r->name);
dbtimestart(buf);
}
//if (db) dblog("adding rand lf %s to cell %d,%d",r->name,c->x,c->y);
if (r) {
if (db) dbtime("doing lf addition");
if (hasflag(r->flags, F_UNIQUE)) {
// does it already exist?
lf = findlfunique(rid);
// if so, move it here, then exit.
if (lf) {
teleportto(lf, c, B_FALSE);
return lf;
}
}
lf = addlf(c, r->id, getrandommonlevel(r, c->map));
if (db) dbtime("finished lf addition");
if (lf) {
flag_t *f;
if (nadded) (*nadded)++;
if (db) dbtime("checking for job");
lf->born = B_FALSE;
if (wantjob == J_NONE) {
if (jobok) {
for (f = lf->flags->first ; f ; f = f->next) {
// has a job?
if (f->id == F_STARTJOB) {
if (rnd(1,100) <= f->val[0]) {
if (f->val[1] == J_RANDOM) {
job_t *j;
j = getrandomjob(B_TRUE);
wantjob = j->id;
} else {
wantjob = f->val[1];
}
givejob(lf, wantjob);
break;
}
}
}
}
} else {
givejob(lf, wantjob);
}
if (autogen) {
// sometimes start off hiding/asleep in new maps
f = lfhasflag(lf, F_STARTHIDDENPCT);
if (f) {
if (rnd(1,100) <= f->val[0]) {
// note: if we start off hidden, we have no hide penalty.
// this is so we can have monsters which start hidden, but
// don't have the 'hide' skill.
addflag(lf->flags, F_HIDING, 0, NA, NA, NULL);
}
}
if (!lfhasflag(lf, F_HIDING) && !lfhasflag(lf, F_DEAF) && cansleep(lf) ) {
int asleepchance = 70;
f = lfhasflag(lf, F_STARTASLEEPPCT);
if (f) {
asleepchance = f->val[0];
}
// TODO: base this on the time, and whether monster is nocturnal
if (pctchance(asleepchance)) {
addflag(lf->flags, F_ASLEEP, B_TRUE, NA, NA, NULL);
}
}
}
// appears in groups?
if (db) dbtime("handling groups");
if (autogen) {
f = hasflag(lf->flags, F_NUMAPPEAR);
if (f) {
// override amount
amt = rnd(f->val[0], f->val[1]);
}
if (amt > 1) {
cell_t *adjcell;
amt--; // we've already added one
//adjcell = c;
for ( ; amt > 0; amt--) {
lifeform_t *newlf;
// find an adjacent cell to one of the newly added monsters,
// starting with the first one
adjcell = real_getrandomadjcell(c, WE_WALKABLE, B_ALLOWEXPAND, LOF_WALLSTOP, NULL);
// did we find one?
if (!adjcell) break;
newlf = addlf(adjcell, r->id, getrandommonlevel(r, adjcell->map));
if (!newlf) {
break;
}
if (nadded) (*nadded)++;
newlf->born = B_FALSE;
// initial monster shoudl remember its minions
addflag(lf->flags, F_MINION, newlf->id, NA, NA, NULL);
// if master is asleep, minions will also be asleep
if (lfhasflag(lf, F_ASLEEP)) {
addflag(newlf->flags, F_ASLEEP, B_TRUE, NA, NA, NULL);
}
// minions never have certain flags.
killflagsofid(newlf->flags, F_DEMANDSBRIBE);
newlf->born = B_TRUE;
}
}
}
// minons?
// appears in groups?
if (db) dbtime("handling minions");
if (autogen) {
f = hasflag(lf->flags, F_MINIONS);
if (f) {
if (rnd(1,100) <= f->val[0]) {
int n;
cell_t *adjcell;
int nminions;
// override amount
nminions = rnd(f->val[1], f->val[2]);
for (n = 0; n < nminions; n++) {
lifeform_t *newlf;
race_t *newr;
adjcell = real_getrandomadjcell(c, WE_WALKABLE, B_ALLOWEXPAND, LOF_WALLSTOP, NULL);
if (!adjcell) break;
newr = findracebyname(f->text);
if (!newr) break;
newlf = addlf(adjcell, newr->id, getrandommonlevel(newr, adjcell->map));
if (!newlf) break;
if (nadded) (*nadded)++;
newlf->born = B_FALSE;
if (lfhasflag(lf, F_ASLEEP)) addflag(newlf->flags, F_ASLEEP, B_TRUE, NA, NA, NULL);
newlf->born = B_TRUE;
}
}
}
}
if (db) dbtime("handling random objects");
// sometimes give the lf random objects (extra monsters through
// 'numappears' don't get them.
if (lfhasflag(lf, F_HUMANOID) && !lfhasflag(lf, F_NOPACK)) {
if (rnd(1,3) == 1) {
int nobs = rnd(1,3);
char buf[BUFLEN];
int i;
for (i = 0; i < nobs; i++) {
if (getrandomob(c->map, buf)) {
object_t *o;
o = addob(lf->pack, buf);
if (o && !canpickup(lf, o, o->amt)) {
killob(o);
}
}
}
}
}
if (db) dbtime("giving torches");
// humanoids on dark levels which can't see will probably have some
// kind of light producing device
if (!islit(c) && !hasflag(lf->flags, F_SEEINDARK) && !hasflag(lf->flags, F_TREMORSENSE)) {
if (lfhasflag(lf, F_HUMANOID)) {
objecttype_t *ot, *poss[MAXCANDIDATES];
int nposs = 0;
// get a random light-producing object
for (ot = objecttype ; ot ; ot = ot->next) {
if (hasflag(ot->flags, F_OPERABLE) &&
hasflagval(ot->flags, F_ACTIVATECONFER, F_PRODUCESLIGHT, NA, NA, NULL)) {
poss[nposs++] = ot;
}
}
if (nposs > 0) {
ot = poss[rnd(0,nposs-1)];
} else {
ot = NULL;
}
if (ot) {
addobfast(lf->pack, ot->id);
}
}
}
if (wantflags) {
// wantflags?
copyflags(lf->flags, wantflags, NA);
}
lf->born = B_TRUE;
} // end if lf
}
// free mem
if (wantflags) {
killflagpile(wantflags);
}
if (db) dbtimeend("finished addmonster");
return lf;
}
object_t *addrandomob(cell_t *c) {
char buf[BUFLEN];
int db = B_FALSE;
object_t *o = NULL;
if (c->type->solid) {
return NULL;
}
if (real_getrandomob(c->map, buf, RO_NONE, NA, NA, c->habitat->id, SZ_MAX)) {
if (db) dblog("adding rand obj %s to cell %d,%d",buf,c->x,c->y);
o = addob(c->obpile, buf);
}
return o;
}
int addrandomthing(cell_t *c, int obchance, int *nadded) {
int rv = TT_NONE;
// if there's already someone there,
// then add an object.
if (!cellwalkable(NULL, c, NULL) || (rnd(1,100) <= obchance)) {
object_t *o;
// object
o = addrandomob(c);
if (o) {
if (nadded) *nadded = o->amt;
rv = TT_OBJECT;
}
} else {
// monster
if (addmonster(c, R_RANDOM, NULL, B_TRUE, 1, B_TRUE, nadded)) {
rv = TT_MONSTER;
}
}
return rv;
}
map_t *getmapindir(map_t *src, int dir) {
map_t *other = NULL;
if (src->nextmap[dir] != -1) {
other = findmap(src->nextmap[dir]);
} else {
if (dir == D_DOWN) {
other = findregionmap(src->region->id, src->depth+1);
} else {
other = findregionmap(src->region->id, src->depth-1);
}
}
return other;
}
//
int getmapmaxvisrange(map_t *m) {
int maxrange;
maxrange = m->habitat->maxvisrange;
// modify for darkness outside ?
if (isoutdoors(m)) {
int hours,mins,secs;
float pct;
splittime(&hours,&mins,&secs);
pct = ((float)mins/59.0) * 100.0;
if (hours == 6) { // ie. 6am - 7am
// getting lighter. as minutes approach 59,
// visrange gets closer to maximum.
maxrange = pctof( pct, maxrange);
limit(&maxrange, 1, NA);
} else if (hours == 18) { // ie. 6pm-7pm
// getting darker. as minutes approach 59,
// visrange gets closer to zero.
maxrange = pctof( 100 - pct, maxrange);
limit(&maxrange, 1, NA);
}
} else if (m->habitat->id == H_DUNGEON) {
// in dungeon, reduce distance based on depth (ie. ambient light)
maxrange -= m->depth;
}
limit(&maxrange, 0, MAXVISRANGE);
return maxrange;
}
// populates retcell[] with all cells within given radius of centre
// if 'scatter' is >0, then not all cells will be returned - as you approach the edge of the radius,
// chances of getting the cells are lowered
void getradiuscells(cell_t *centre, int radius, int dirtype, enum LOFTYPE needlof, int wantcentre, cell_t **retcell, int *ncells, int scatterdensity) {
int (*distfunc)(cell_t *, cell_t *);
int x,y;
cell_t *c;
*ncells = 0;
if (!centre) {
return;
}
if (dirtype == DT_ORTH) {
distfunc = getcelldistorth;
} else {
distfunc = getcelldist;
}
for (y = centre->y - radius; y <= centre->y + radius; y++) {
for (x = centre->x - radius; x <= centre->x + radius; x++) {
c = getcellat(centre->map, x,y);
if (c) {
int distance;
distance = distfunc(centre, c);
if (haslof(centre, c, needlof, NULL) && (distance <= radius) &&
(wantcentre || (c != centre)) ) {
int chance;
if (scatterdensity) {
chance = 100 - (((float)distance / (float)radius) * scatterdensity);
} else {
chance = 100;
}
if (pctchance(chance)) {
retcell[*ncells] = c;
(*ncells)++;
}
}
}
}
}
}
int getroomid(cell_t *c) {
if (c->room) {
return c->room->id;
}
return -1;
}
// whichside should be D_ORTH
// for now, this function will NOT include room corners
void getroomedge(map_t *map, int roomid, int minx, int miny, int maxx, int maxy, int whichside, cell_t **retcell, int *ncells, int onlywantsolid) {
int x,y;
cell_t *c;
*ncells = 0;
if (whichside == D_N) {
y = miny;
for (x = minx+1; x <= maxx-1; x++) {
c = getcellat(map, x, y);
if (!onlywantsolid || c->type->solid) {
retcell[*ncells] = getcellat(map, x, y);
(*ncells)++;
}
}
} else if (whichside == D_S) {
y = maxy;
for (x = minx+1; x <= maxx-1; x++) {
c = getcellat(map, x, y);
if (!onlywantsolid || c->type->solid) {
retcell[*ncells] = getcellat(map, x, y);
(*ncells)++;
}
}
} else if (whichside == D_W) {
x = minx;
for (y = miny+1; y <= maxy-1; y++) {
c = getcellat(map, x, y);
if (!onlywantsolid || c->type->solid) {
retcell[*ncells] = getcellat(map, x, y);
(*ncells)++;
}
}
} else if (whichside == D_E) {
x = maxx;
for (y = miny+1; y <= maxy-1; y++) {
c = getcellat(map, x, y);
if (!onlywantsolid || c->type->solid) {
retcell[*ncells] = getcellat(map, x, y);
(*ncells)++;
}
}
}
}
// if outlineid is -1, it's automatically assigned
region_t *addregion(enum REGIONTYPE rtype, region_t *parent, int outlineid) {
region_t *a;
regionoutline_t *ro,*poss[MAXCANDIDATES];
int nposs = 0;
int id;
// is there already a region?
if (lastregion) {
id = lastregion->id + 1;
} else {
id = 0;
}
// add to the end of the list
if (firstregion == NULL) {
firstregion = malloc(sizeof(region_t));
a = firstregion;
a->prev = NULL;
} else {
// go to end of list
a = lastregion;
a->next = malloc(sizeof(region_t));
a->next->prev = a;
a = a->next;
}
lastregion = a;
a->next = NULL;
// props
a->id = id;
a->rtype = findregiontype(rtype);
a->parentregion = parent;
if (outlineid == -1) {
// randomly assign a regionoutline
for (ro = firstregionoutline; ro ; ro = ro->next) {
if (ro->rtype->id == rtype) {
poss[nposs++] = ro;
}
}
// make sure we got one...
if (nposs) {
a->outline = poss[rnd(0,nposs-1)];
} else {
a->outline = NULL;
}
} else {
a->outline = findoutline(outlineid);
assert(a->outline);
}
return a;
}
regionoutline_t *addregionoutline(enum REGIONTYPE rtype) {
regionoutline_t *a;
// add to the end of the list
if (firstregionoutline == NULL) {
firstregionoutline = malloc(sizeof(regionoutline_t));
a = firstregionoutline;
a->prev = NULL;
} else {
// go to end of list
a = lastregionoutline;
a->next = malloc(sizeof(regionoutline_t));
a->next->prev = a;
a = a->next;
}
lastregionoutline = a;
a->next = NULL;
// props
if (a == firstregionoutline) {
a->id = 0;
} else {
a->id = lastregionoutline->id + 1;
}
a->rtype = findregiontype(rtype);
a->nthings = 0;
return a;
}
regionthing_t *addregionthing(regionoutline_t *ro, int depth, int x, int y, enum REGIONTHING whatkind, int value, char *what) {
regionthing_t *rt;
rt = &(ro->thing[ro->nthings]);
rt->depth = depth;
rt->x = x;
rt->y = y;
rt->whatkind = whatkind;
rt->value = value;
if (what) {
rt->what = strdup(what);
} else {
rt->what = strdup("");
}
ro->nthings++;
if (ro->nthings >= MAXOUTLINETHINGS) {
dblog("error - too many outlinethings!");
printf("error - too many outlinethings!\n");
exit(1);
}
return rt;
}
regiontype_t *addregiontype(enum REGIONTYPE id, char *name, enum HABITAT defaulthabitat, int maxdepth, int stairsperlev, int deeperdir, int major) {
regiontype_t *a;
// add to the end of the list
if (firstregiontype == NULL) {
firstregiontype = malloc(sizeof(regiontype_t));
a = firstregiontype;
a->prev = NULL;
} else {
// go to end of list
a = lastregiontype;
a->next = malloc(sizeof(regiontype_t));
a->next->prev = a;
a = a->next;
}
lastregiontype = a;
a->next = NULL;
// props
a->id = id;
a->name = strdup(name);
a->defaulthabitat = defaulthabitat;
a->maxdepth = maxdepth;
a->stairsperlev = stairsperlev;
a->deeperdir = deeperdir;
a->majorbranch = major;
return a;
}
int autodoors(map_t *map, int roomid, int minx, int miny, int maxx, int maxy, int doorpct, int dooropenchance) {
int i,d;
cell_t *poss[MAXCANDIDATES], *cell[MAXCANDIDATES]; // TODO: should this be maxroomw * maxroomh ?
int ncells = 0, npossible = 0;
int doorsadded = 0;
int db = B_FALSE;
// for each side, make list of all possible door locations
// then pick one randomly.
// BUT if the nearby corridor only has one exit, always
// place a door.
for (d = D_N; d <= D_W; d++) {
npossible = 0;
getroomedge(map, roomid, minx, miny, maxx, maxy, d, cell, &ncells, B_TRUE);
for (i = 0; i < ncells; i++) {
cell_t *newcell;
// is there empty space on the other side of this wall segment?
newcell = getcellindir(cell[i], d);
if (newcell && !newcell->type->solid) {
int doorcount;
// if so, make sure there are no other adjacent doors
doorcount = countadjcellswithflag(cell[i], F_DOOR, DT_ORTH);
if (doorcount == 0) {
// if there is only one way out of the adjacent empty cell, and
// walls to either side of the potential door location, then
// always add a door
if ((countcellexits(newcell) == 1) && wallstoleftright(newcell, d)) {
if (rnd(1,100) <= doorpct) {
makedoor(cell[i], dooropenchance);
} else {
setcelltype(cell[i], cell[i]->habitat->emptycelltype);
addflag(map->flags, F_ROOMEXIT, roomid, cell[i]->x, cell[i]->y, NULL);
}
} else {
// otherwise mark this as a _potential_ door location.
poss[npossible] = cell[i];
npossible++;
}
}
}
}
// if we have potential door locations, add one somewhere along this wall.
if (npossible > 0) {
int sel;
sel = rnd(0,npossible-1);
if (rnd(1,100) <= doorpct) {
makedoor(poss[sel], dooropenchance);
} else {
setcelltype(poss[sel], poss[sel]->habitat->emptycelltype);
addflag(map->flags, F_ROOMEXIT, roomid, poss[sel]->x, poss[sel]->y, NULL);
}
doorsadded++;
}
} // end foreach direction
if (doorsadded == 0) {
int i,d,used[MAXDIR_ORTH],poss[MAXDIR_ORTH];
int dodoor[MAXDIR_ORTH];
int ndodoors = 0;
int ndoors,nposs = 0;
int sel;
//
for (d = D_N; d <= D_W; d++) {
used[d] = B_FALSE;
}
// no possible door locations - add a random number
ndoors = rnd(1,4);
for (i = 0; i < ndoors; i++) {
// find a dir we haven't already used
nposs = 0;
for (d = D_N; d <= D_W; d++) {
if (!used[d]) poss[nposs++] = d;
}
if (nposs <= 0) {
break;
}
sel = rnd(0,nposs-1);
used[poss[sel]] = B_TRUE;
dodoor[ndodoors++] = poss[sel];
}
// actually make the doors
for (i = 0; i < ndodoors; i++) {
int sel;
d = dodoor[i];
getroomedge(map, roomid, minx, miny, maxx, maxy, d, cell, &ncells, B_TRUE);
if (ncells) {
sel = rnd(0,ncells-1);
if (rnd(1,100) <= doorpct) {
makedoor(cell[sel], dooropenchance);
doorsadded++;
} else {
setcelltype(cell[sel], cell[sel]->habitat->emptycelltype);
addflag(map->flags, F_ROOMEXIT, roomid, cell[sel]->x, cell[sel]->y, NULL);
doorsadded++;
}
}
}
}
if (db) dblog("autodoors() added %d doors to roomid %d", doorsadded, roomid);
return doorsadded;
}
int cellhaslos(cell_t *c1, cell_t *dest) {
int deltax, deltay;
int numpixels;
int d;
int dinc1,dinc2,xinc1,xinc2,yinc1,yinc2;
int xinc,yinc,dinc;
int i;
int x1,y1;
int x;
int y;
//int wentuphill = B_FALSE;
//int origheight;
//int shopwall;
int x2,y2;
map_t *map;
if (!dest) return B_FALSE;
// let the player see when dead, otherwise the screen wil
// go black when "You die" appears.
map = c1->map;
x1 = c1->x;
y1 = c1->y;
x2 = dest->x;
y2 = dest->y;
deltax = (x2 - x1);
if (deltax < 0) deltax = -deltax;
deltay = (y2 - y1);
if (deltay < 0) deltay = -deltay;
// can always see your own cell
if ((deltax == 0) && (deltay == 0)) {
return B_TRUE;
}
if (deltax >= deltay) {
numpixels = deltax + 1;
d = (deltay*2) - deltax;
dinc1 = deltay << 1;
dinc2 = (deltay-deltax) << 1;
xinc1 = 1;
xinc2 = 1;
yinc1 = 0;
yinc2 = 1;
} else {
numpixels = deltay + 1;
d = (deltax*2) - deltay;
dinc1 = deltax << 1;
dinc2 = (deltax - deltay) << 1;
xinc1 = 0;
xinc2 = 1;
yinc1 = 1;
yinc2 = 1;
}
if (x1 > x2) {
xinc1 = - xinc1;
xinc2 = - xinc2;
}
if (y1 > y2) {
yinc1 = - yinc1;
yinc2 = - yinc2;
}
x = x1; y = y1;
for (i = 0; i < numpixels ; i++) {
cell_t *cell;
// don't need to move out of the last one
if ((x == x2) && (y == y2)) {
break;
}
if (d < 0) {
xinc = xinc1;
yinc = yinc1;
dinc = dinc1;
} else {
xinc = xinc2;
yinc = yinc2;
dinc = dinc2;
}
// you can always see your own cell
if (i != 0) {
// solid cells stop los - but if you are standing on a solid
// cell you can still see out.
cell = getcellat(map, x, y);
if (!cell->type->transparent) {
return B_FALSE;
}
/*
// check for smoke
if ((x != x1) || (y != y1)) { // if not in first cell
if (hasopaqueobject(viewer, x,y,z) && (getheight(x,y,z) >= origheight)) {
if (!hasproplf(viewer, P_SEEINSMOKE)) {
return B_FALSE;
}
}
}
*/
// check for objects which block view
if (hasobwithflag(cell->obpile, F_BLOCKSVIEW)) {
return B_FALSE;
}
}
// move to next cell
d += dinc;
x += xinc;
y += yinc;
}
// made it to the target cell!
return B_TRUE;
}
void clearcell(cell_t *c) {
// kill everything there - (lifeforms && objects)
if (c->lf && !isplayer(c->lf)) {
killlf(c->lf);
}
while (c->obpile->first) {
killob(c->obpile->first);
}
if (gamemode == GM_GAMESTARTED) {
c->known = B_FALSE;
c->knownglyph.ch = ' ';
c->knownglyph.colour = C_GREY;
}
}
void clearcell_exceptflags(cell_t *c, ... ) {
va_list args;
enum FLAG exception[MAXCANDIDATES];
int nexceptions = 0,i;
object_t *o,*nexto;
va_start(args, c);
exception[nexceptions] = va_arg(args, enum FLAG);
while (exception[nexceptions] != F_NONE) {
nexceptions++;
exception[nexceptions] = va_arg(args, enum FLAG);
}
va_end(args);
assert(nexceptions < MAXCANDIDATES);
if (c->lf && !isplayer(c->lf)) {
killlf(c->lf);
}
for (o = c->obpile->first ; o ; o = nexto) {
int exclude = B_FALSE;
nexto = o->next;
for (i = 0; i < nexceptions; i++) {
if (hasflag(o->flags, exception[i])) {
exclude = B_TRUE;
break;
}
}
if (!exclude) killob(o);
}
if (gamemode == GM_GAMESTARTED) {
c->known = B_FALSE;
c->knownglyph.ch = ' ';
c->knownglyph.colour = C_GREY;
}
}
// returns true if something happened
int dowaterspread(cell_t *c) {
float thisdepth;
int i;
int nsurround = 0;
int db = B_FALSE;
cell_t *surroundcell[8];
if (!c || c->type->solid || hascloseddoor(c)) {
return B_FALSE;
}
// calculate depth of this cell
thisdepth = getcellwaterdepth(c, NULL);
if (!thisdepth) return B_FALSE;
// count surrounding cells of lower depth
for (i = DC_N; i <= DC_NW; i++) {
cell_t *newc;
newc = getcellindir(c, i);
if (newc && !newc->type->solid && !hascloseddoor(newc)) {
float newcdepth;
int ok = B_FALSE;
/*
FIX THIS CODE LATER
if (newc->type->floorheight == c->type->floorheight) {
// same height - don't include these???
ok = B_FALSE;
//ok = B_TRUE;
} else if (newc->type->floorheight < c->type->floorheight) {
// ie. downhill. don't include these
ok = B_FALSE;
} else if (newc->type->floorheight > c->type->floorheight) {
// ie. uphill.
ok = B_TRUE;
}
*/
if (newc->type->floorheight == c->type->floorheight) {
ok = B_TRUE;
} else {
ok = B_FALSE;
}
if (ok) {
newcdepth = getcellwaterdepth(newc, NULL);
if (newcdepth < thisdepth) {
surroundcell[nsurround] = newc;
nsurround++;
}
}
}
}
// if there were any posisble cells around us
if (nsurround) {
float newdepth;
//newdepth = thisdepth / (pctof(nsurround,50)+1);
newdepth = thisdepth / (pctof(nsurround,55)+1);
//newdepth = thisdepth / (nsurround+1);
limitf(&newdepth,(float)DP_NONE, (float)DP_MAX);
if (db) dblog("waterspread: cell %d,%d (%d/%s) spreads to %d cells-->%d/%s",
c->x, c->y, (int)thisdepth, getwaterdepthname((int)thisdepth),
nsurround, (int)newdepth, getwaterdepthname((int)newdepth));
// SET our cell to new depth
setwaterdepth(c, (int)newdepth);
// ADD water to surrounding cells
for (i = 0; i < nsurround; i++) {
cell_t *sc;
flag_t *f;
sc = surroundcell[i];
f = hasflagval(sc->map->flags, F_NEWWATERDEPTH, sc->x, sc->y, NA, NULL);
if (f) {
f->val[2] += newdepth;
} else {
addflag(sc->map->flags, F_NEWWATERDEPTH, sc->x, sc->y, (int)newdepth, NULL);
}
}
return B_TRUE;
}
return B_FALSE;
}
void fix_reachability(map_t *m) {
int i,keepgoing = B_TRUE, nfixed = 0;
int db = B_TRUE;
cell_t *c = NULL;
if (db) dblog("fix_reachability starting.");
switch (m->habitat->id) {
case H_FOREST:
case H_HEAVEN:
case H_PIT:
case H_VILLAGE:
if (db) dblog("fix_reachability not required for this habitat.");
return;
default: break;
}
// find first non-empty cell
for (i = 0; i < m->w * m->h; i++) {
if (!m->cell[i]->type->solid) {
c = m->cell[i];
break;
}
}
// no empty cells in map?
if (!c) return;
while (keepgoing) {
keepgoing = B_FALSE;
// mark all cells as non-filled
for (i = 0; i < m->w * m->h; i++) {
m->cell[i]->filled = FALSE;
}
// floodfill
floodfill(c);
// any remaining non-filled empty cells?
for (i = 0; i < m->w * m->h; i++) {
if (!m->cell[i]->type->solid && !m->cell[i]->filled) {
int nadded = 0;
// found an unreachable cell! link it back to a filled cell.
if (db) dblog(" found unreachable area at %d,%d. will fix it.",
m->cell[i]->x, m->cell[i]->y);
linkexit(m->cell[i], B_TRUE, &nadded);
if (db) dblog(" fixed unreachable area by adding %d cells.", nadded);
assert(nadded);
// now run the test again.
// 'c' will be where the next flood will will happen.
keepgoing = B_TRUE;
c = m->cell[i];
nfixed++;
break;
}
}
}
if (db) dblog(" fix_reachability complete. fixed %d unreachable areas.", nfixed);
}
void floodfill(cell_t *startcell) {
int d;
if (startcell && // not off the map
!startcell->type->solid && // empty cell
!startcell->filled) { // not already filled
startcell->filled = B_TRUE;
} else {
return;
}
for (d = DC_N; d <= DC_NW; d++) {
floodfill(getcellindir(startcell, d)); // recursive call
}
}
cell_t *getcellat(map_t *map, int x, int y) {
if (!isonmap(map, x, y)) return NULL;
return map->cell[y*map->w + x];
}
int getcelldist(cell_t *src, cell_t *dst) {
double xd,yd;
// use pythag
xd = abs(dst->x - src->x);
yd = abs(dst->y - src->y);
return (int)sqrt(xd*xd + yd*yd);
}
int getcelldistorth(cell_t *src, cell_t *dst) { // add x/y
return abs(dst->x - src->x) + abs(dst->y - src->y);
}
//populates 'g' with the contects of cell 'c', as seen by 'viewer'
// if we don't have LOS to there, set g->ch to NUL.
void getcellglyph(glyph_t *g, cell_t *c, lifeform_t *viewer) {
glyph_t tempgl;
// default
g->ch = ' ';
g->colour = C_RED;
if (haslos(viewer, c)) {
// show cell contents
if (c->lf && cansee(viewer, c->lf)) { // lifeform here which we can see
// draw the lf's race glyph
*g = *(getlfglyph(c->lf));
if (isprone(c->lf)) {
g->ch = flip(g->ch);
}
if (lfhasflag(c->lf, F_FROZEN)) {
g->colour = C_CYAN;
}
return;
} else { // we can see the floor here
void *thing;
// scanned lf here?
if (isinscanrange(c, &thing, NULL, &tempgl) == TT_MONSTER) {
*g = tempgl;
//mvwprintw(gamewin, y-viewy, x-viewx, "%c", glyph);
//drawglyph(&glyph, x, y);
return;
}
// otherwise fall through
}
// objects here?
if ((countobs(c->obpile, B_FALSE) > 0)) {
object_t *o;
// draw highest object in sort order
o = gettopobject(c, B_TRUE);
if (o && !hasflag(o->flags, F_NOGLYPH)) {
// return the object's glyph
*g = *(getglyph(o));
} else {
// objects here, but we can't see them. draw the cell.
*g = c->type->glyph;
if (!islit(c)) {
g->colour = C_BLUE;
}
}
} else {
// draw cell normally
//drawcell(cell, x, y);
*g = c->type->glyph;
if (!islit(c)) {
g->colour = C_BLUE;
}
}
} else { // can't see the cell
void *thing;
//drawscannedcell(cell, x-viewx, y-viewy);
switch (isinscanrange(c, &thing, NULL, &tempgl)) {
case TT_MONSTER:
case TT_OBJECT:
*g = tempgl;
break;
default:
if (c->known) {
// copy from cell
/*
*g = c->type->glyph;
*/
*g = c->knownglyph;
if (g->ch == '.') {
g->ch = ' ';
}
// out of LOS - show as dark
// TODO: if terminal supports it, use C_DARKGREY instead.
g->colour = C_BLUE;
}
break;
}
}
}
enum DEPTH getcellwaterdepth(cell_t *c, lifeform_t *lf) {
object_t *o;
o = hasobwithflag(c->obpile, F_DEEPWATER);
if (o) {
return getobdepth(o, lf);
}
return DP_NONE;
}
int getdoorlockdiff(int depth) {
return 19 + depth;
}
int getdoorsecretdiff(int depth) {
return 20 + (depth / 2);
}
flag_t *getmapcoords(map_t *m, int *x, int *y) {
flag_t *f;
f = hasflag(m->flags, F_MAPCOORDS);
if (f) {
if (x) *x = f->val[0];
if (y) *y = f->val[1];
return f;
}
if (x) *x = -999;
if (y) *y = -999;
return NULL;
}
int getmapdifficulty(map_t *m) {
int diff = 1;
if (m) {
if (isoutdoors(m)) {
int x,y;
// depth is distance from 0,0
getmapcoords(m, &x, &y);
diff = (abs(x) + abs(y))+1;
} else {
diff = m->depth;
}
} else {
diff = rnd(1,MAXDEPTH);
}
return diff;
}
// forglyph will be true if we are using this function for the purpose
// of figuring out which glyph to draw
object_t *gettopobject(cell_t *where, int forglyph) {
object_t *o,*oo = NULL;
enum LFSIZE largest = SZ_MINI;
int c;
// get largest known impassable object first
for (o = where->obpile->first ; o ; o = o->next) {
flag_t *f;
// ignore hidden traps, but not secret doors
if (hasflag(o->flags, F_SECRET) && !isdoor(o, NULL)) {
} else if (hasflag(o->flags, F_TRAIL) && !canseeob(player, o)) {
} else if (forglyph && hasflag(o->flags, F_NOGLYPH)) {
} else {
f = hasflag(o->flags, F_IMPASSABLE);
if (f && (f->val[1] > largest)) {
largest = f->val[1];
oo = o;
}
}
}
if (oo) {
return oo;
}
// otherwise draw highest object in sort order
c = 0;
while (sortorder[c] != OC_NULL) {
// check each object against this ob class
// count backwards so more recently dropped objects
// appear first.
for (o = where->obpile->last ; o ; o = o->prev) {
if (o->type->obclass->id == sortorder[c]) {
if (hasflag(o->flags, F_SECRET)) {
} else if (hasflag(o->flags, F_TRAIL) && !canseeob(player, o)) {
} else if (forglyph && hasflag(o->flags, F_NOGLYPH)) {
} else {
return o;
}
}
}
c++;
}
return NULL;
}
void calclight(map_t *map) {
int x,y;
cell_t *c;
int i;
// remember lit values for cells in player's los
if (gamemode == GM_GAMESTARTED) {
for (i = 0; i < player->nlos; i++) {
player->los[i]->lastlit = player->los[i]->lit;
}
}
for (y = 0; y < map->h; y++) {
for (x = 0; x < map->w; x++) {
c = getcellat(map, x,y);
//if (c && (c->lit == L_PERMLIGHT) && (c->lit != L_PERMDARK)) c->lit = B_FALSE;
c->lastlit = c->lit;
if (c && (c->lit != L_PERMLIGHT) && (c->lit != L_PERMDARK)) c->lit = B_FALSE;
}
}
for (y = 0; y < map->h; y++) {
for (x = 0; x < map->w; x++) {
c = getcellat(map, x,y);
if (c) {
int radius;
object_t *o;
// lit based on depth
if (isoutdoors(map)) {
int hours,mins,secs;
splittime(&hours,&mins,&secs);
if (isnighttime()) {
// ie. nighttime, after 7pm or before 5am
} else {
// ie. daytime
makelit(c, L_PERMLIGHT, -1);
}
} else {
if ((map->depth <= 5) && (c->lit != L_PERMDARK)) {
makelit(c, L_PERMLIGHT, -1);
}
}
// TODO: has dark producing lf?
// TODO: has dark producing object?
// has lightproducing lf? (ie.hasflag f_produceslight)
if (c->lf) {
radius = lfproduceslight(c->lf);
if (radius) {
makelitradius(c, radius, L_TEMP, -1);
}
}
// has light-producing object on ground?
for (o = c->obpile->first ; o ; o = o->next) {
radius = obproduceslight(o);
if (radius) {
makelitradius(c, radius, L_TEMP, -1);
}
}
}
}
}
// did any lit values within player's los change?
if (gamemode == GM_GAMESTARTED) {
for (i = 0; i < player->nlos; i++) {
if (player->los[i]->lastlit != player->los[i]->lit) {
needredraw = B_TRUE;
break;
}
}
}
}
int calcroompos(map_t *map, int w, int h, int xmargin, int ymargin, int *bx, int *by, int force) {
int x,y,i;
int bestscore = 9888;
coord_t coord[MAX_MAPW*MAX_MAPH];
int coordscore[MAX_MAPW*MAX_MAPH];
int ncoords = 0;
coord_t poss[MAX_MAPW*MAX_MAPH];
int nposs = 0;
cell_t *cell;
int sel;
int db = B_FALSE;
int foundvalid = B_FALSE;
// init coords list
for (y = 1 + ymargin; y < (map->h - ymargin); y++) {
for (x = 1 + xmargin; x < (map->w - xmargin); x++) {
coord[ncoords].x = x;
coord[ncoords].y = y;
ncoords++;
}
}
// try placing room at all positions
for (i = 0; i < ncoords; i++) {
int score = 9999;
x = coord[i].x;
y = coord[i].y;
// would the room fit here?
if ( ((x + w) <= (map->w - xmargin)) &&
((y + h) <= (map->h - ymargin))) {
int valid = B_TRUE;
int rx,ry;
score = 0;
// calculate score if we placed the room with its top left corner here.
for (ry = y; (ry < y+h) && valid; ry++) {
for (rx = x; (rx < x+w) && valid; rx++) {
int includethiscell = B_FALSE;
cell = getcellat(map, rx,ry);
// is this cell adjacent to an empty cell and not a
// corner (ie. a valid door location)
if (countcellexits(cell)) {
score++;
if ( ((ry == y) && (rx == x)) ||
((ry == y) && (rx == (x+w-1))) ||
((ry == y+h-1) && (rx == x)) ||
((ry == y+h-1) && (rx == (x+w-1))) ) {
// corner. don't check this cell for scores.
} else {
includethiscell = B_TRUE;
}
}
if (includethiscell) {
// is this cell empty itself?
if (!cell->type->solid) score += 3;
// avoid being adjacent to other room walls
if (countcellexits(cell)) score++;
score += (countadjrooms(cell)*3);
// overlapping another room?
if (isroom(cell)) {
if (force) {
score += 10;
} else {
valid = B_FALSE;
}
}
// NEVER create it on top of the player!
// (normally this can't happen, but debugging via 'create
// vault' could do it)
if (cell->lf && isplayer(cell->lf)) {
valid = B_FALSE;
}
}
}
}
if (valid) {
if (score < bestscore) {
bestscore = score;
foundvalid = B_TRUE;
}
} else {
score = 9999;
}
}
coordscore[i] = score;
if (db) dblog("cell %d,%d - score %d",x,y,score);
}
if (foundvalid) {
// now go through and make a list of all BEST positions
nposs = 0;
for (i = 0; i < ncoords; i++) {
if (coordscore[i] == bestscore) {
poss[nposs++] = coord[i];
}
}
} else {
nposs = 0;
}
if (nposs == 0) {
*bx = -1;
*by = -1;
return B_TRUE;
} else {
// pick a random one
sel = rnd(0,nposs-1);
if (db) dblog("calcroompos - %d possibilities (bestscore=%d), selected #%d.", nposs, bestscore, sel);
*bx = poss[sel].x;
*by = poss[sel].y;
}
return B_FALSE;
}
int countadjcellswithflag(cell_t *cell, enum FLAG fid, int dirtype) {
int d;
int count = 0;
int start, end;
cell_t *newcell;
if (dirtype == DT_ORTH) {
start = D_N;
end = D_W;
} else {
start = DC_N;
end = DC_NW;
}
for (d = start; d <= end; d++) {
newcell = getcellindir(cell, d);
if (newcell && hasobwithflag(newcell->obpile, fid)) {
count++;
}
}
return count;
}
int countadjcellsoftype(cell_t *cell, int id) {
int d;
int count = 0;
cell_t *newcell;
for (d = D_N; d < MAXDIR_ORTH; d++) {
newcell = getcellindir(cell, d);
if (newcell && newcell->type->id == id) {
count++;
}
}
return count;
}
int countadjrooms(cell_t *cell) {
int d;
int count = 0;
cell_t *newcell;
for (d = D_N; d < MAXDIR_ORTH; d++) {
newcell = getcellindir(cell, d);
if (newcell && isroom(newcell)) {
count++;
}
}
return count;
}
int countadjwalls(cell_t *cell) {
int d;
int walls = 0;
for (d = DC_N; d <= DC_NW; d++) {
cell_t *newcell;
newcell = getcellindir(cell, d);
if (!newcell || newcell->type->solid) {
walls++;
}
}
return walls;
}
int countcellexits(cell_t *cell) {
int d;
int exits = 0;
cell_t *newcell;
assert(cell);
for (d = D_N; d < MAXDIR_ORTH; d++) {
newcell = getcellindir(cell, d);
if (newcell && !newcell->type->solid) {
exits++;
}
}
return exits;
}
int countcellexitsfor(lifeform_t *lf) {
int d;
int exits = 0;
assert(lf);
for (d = DC_N; d <= DC_NW; d++) {
if (moveclear(lf, d, NULL)) {
exits++;
}
}
return exits;
}
int countlfs(map_t *map) {
lifeform_t *lf;
int count = 0;
for (lf = map->lf ; lf ; lf = lf->next) {
count++;
}
return count;
}
int countmapobs(map_t *m, enum OBTYPE oid) {
cell_t *c;
int count = 0,x,y;
for (y = 0; y < m->h; y++) {
for (x = 0; x < m->w; x++) {
c = getcellat(m, x, y);
if (c) {
count += countobsoftype(c->obpile, oid);
}
}
}
return count;
}
//
void createdungeon(map_t *map, int depth, map_t *parentmap, int exitdir, object_t *entryob) {
int wantrooms = B_TRUE;
int d;
int x,y;
int i;
int done,unused;
int dir;
int lastdir;
int numrooms = 0;
int roomw[MAXROOMS],roomh[MAXROOMS];
vault_t *roomvault[MAXROOMS];
//int roomspecial[MAX_MAPROOMS];
//int bestx,besty;
//int w,h;
//int startdir,forcex,forcey,ntries;
cell_t *cell, *c;
object_t *o;
int db = B_FALSE;
// parameters
int turnpct = DEF_TURNPCT;
int sparseness = DEF_SPARSENESS;
int looppct = DEF_LOOPPCT;
int minrooms = MINROOMS;
int maxrooms = MAXROOMS;
int moved = 0;
enum CELLTYPE emptycell,solidcell;
// fill entire maze with walls
for (y = 0; y < map->h; y++) {
for (x = 0; x < map->w; x++) {
addcell(map, x, y);
}
}
// what kind of cells will 'empty' ones be?
emptycell = map->habitat->emptycelltype;
solidcell = map->habitat->solidcelltype;
// pick initial random spot
cell = getrandomcell(map);
setcelltype(cell, emptycell);
cell->visited = B_TRUE;
//if (db) printf("- Starting (%d,%d)\n",cell->x, cell->y);
lastdir = D_UNKNOWN;
done = B_FALSE;
dir = D_NONE;
while (!done) {
// get random direction based on turnpct
dir = D_NONE;
while (dir == D_NONE) {
int badcount;
//if (db) printf("- At (%d,%d), moved %d, finding new direction...\n",cell->x, cell->y, moved);
dir = getnewdigdir(cell, lastdir, (moved < 2) ? 0 : turnpct, &moved);
badcount = 0;
while (dir == D_NONE) {
badcount++;
if (badcount > 10) {
// finish!
done = B_TRUE;
break;
}
// pick new EMPTY random spot
cell = getrandomcell(map);
while (!isempty(cell)) {
cell = getrandomcell(map);
}
//if (db) printf("--- Couldn't find a valid direction. Jumped to (%d,%d).\n",cell->x, cell->y);
// pick a new random dir
dir = getnewdigdir(cell, lastdir, turnpct, &moved);
}
if (!done) {
//if (db) printf("- Digging %s from (%d,%d).\n",getdirname(dir),cell->x, cell->y);
}
}
if (!done) {
// move to adjacent cell in the given direction
cell = getcellindir(cell, dir);
//if (db) printf("- Now at (%d,%d)\n",cell->x, cell->y);
moved++;
// blank it
setcelltype(cell,emptycell);
cell->visited = B_TRUE;
// mark surrounding cells as visited
for (d = DC_N; d < MAXDIR_COMPASS; d++) {
cell_t *thiscell;
thiscell = getcellindir(cell, d);
if (thiscell) {
//if (db) printf("* Marking surrounding cell in dir %d (%d,%d) as visited.\n",d, thiscell->x, thiscell->y);
thiscell->visited = B_TRUE;
}
}
// remember last direction
lastdir = dir;
// check if we have visited all valid cells
unused = 0;
for (y = 0; y < map->h; y++) {
for (x = 0; x < map->w; x++) {
cell_t *thiscell;
thiscell = getcellat(map, x, y);
if (!thiscell->visited) {
unused++;
}
}
}
if (!unused) {
done = B_TRUE;
}
}
//printf("%d unused cell(s)\n",unused);
//dumpmap(map);
//getchar();
}
// use sparseness to cut down dead ends
remove_deadends(map, sparseness);
// introduce loops
for (y = 0; y < map->h; y++) {
for (x = 0; x < map->w; x++) {
cell = getcellat(map, x, y);
if (!cell->type->solid && countcellexits(cell) == 1) {
// dead end - maybe make loop from here
if (rnd(1,100) <= looppct) {
int connected = B_FALSE;
int loopok = B_TRUE;
int dir;
// pick a random directory
dir = getnewdigdir(cell, D_UNKNOWN,100, &moved);
if (dir == D_NONE) {
// can't make a loop from here.
loopok = B_FALSE;
} else {
int tries = 0;
// if we go this dir, will we hit a
// corridor?
while (!isloopdirok(cell, dir)) {
tries++;
// tried every direction?
if (tries >= MAXDIR_ORTH) {
loopok = B_FALSE;
break;
}
// turn...
if (++dir >= MAXDIR_ORTH) {
dir = 0;
}
}
}
if (loopok) {
// keep digging until we hit another corridor
while (!connected) {
cell_t *newcell;
// find adjacent cell in the given direction
newcell = getcellindir(cell, dir);
if (!newcell) {
connected = B_TRUE;
} else {
// have we hit another corridor yet?
if (!newcell->type->solid) {
connected = B_TRUE;
} else {
// blank adjacent cell
setcelltype(newcell, emptycell);
newcell->visited = B_TRUE;
}
cell = newcell;
}
}
}
}
}
}
}
// adjust min/maxrooms based on fixed vaults
minrooms -= map->nfixedrooms;
maxrooms -= map->nfixedrooms;
limit(&minrooms, 0, NA);
limit(&maxrooms, 0, NA);
// create rooms
if (wantrooms && (maxrooms > 0)) {
numrooms = rnd(minrooms, maxrooms);
//printf("using %d rooms\n",numrooms);
//dblog("Adding %d rooms...\n",numrooms);
for (i = 0; i < numrooms; i++) {
// maybe make it a special room
roomvault[i] = NULL;
if (rnd(1,100) <= map->habitat->randvaultpct) {
vault_t *v;
v = getvaulttype(map);
if (createvault(map, i, v, &roomw[i],&roomh[i], NULL, NULL)) {
// failed
} else {
// success
roomvault[i] = v;
}
}
if (!roomvault[i]) {
int rx,ry;
// just do a normal room
createroom(map, i, NA, NA, DEF_VAULTMARGIN, DEF_VAULTMARGIN, &rx, &ry, &roomw[i],&roomh[i], 50, B_FALSE);
roomvault[i] = B_FALSE;
}
}
}
// now clear up dead ends again.
remove_deadends(map, sparseness);
// link up room exits
for (i = 0; i < map->nrooms; i++) {
if (!map->room[i].exitslinked) {
linkexits(map, map->room[i].id);
}
}
// add staircases.
// first dungeon level has 1 up stairs, 3 down.
// subsequent levels always have 3 up and down stairs
// UP STAIRS
if (map->depth == 1) {
flag_t *f;
// first region level. just one exit stairs
c = NULL;
while (!c || !isempty(c) || countobs(c->obpile, B_TRUE)) {
c = getrandomroomcell(map, ANYROOM);
}
o = addobfast(c->obpile, OT_STAIRSUP);
// have to force these stairs to go back to a different region.
f = hasflag(o->flags, F_CLIMBABLE);
f->val[1] = map->region->parentregion->id;
linkstairs(o, NULL);
// special case: first dungeon level has barriers over the exit stairs
if (map->region->rtype->id == RG_FIRSTDUNGEON) {
if (c->lf) killlf(c->lf);
addobfast(c->obpile, OT_MAGICBARRIER);
// also clear all the cells around it to prevent reachability errors
for (d = DC_N; d <= DC_NW; d++) {
cell_t *c2;
c2 = getcellindir(c, d);
if (c2) setcelltype(c2, map->habitat->emptycelltype);
}
}
} else {
for (i = 0; i < map->region->rtype->stairsperlev; i++) {
c = NULL;
while (!c || !isempty(c) || countobs(c->obpile, B_TRUE)) {
c = getrandomroomcell(map, ANYROOM);
}
o = addobfast(c->obpile, OT_STAIRSUP);
linkstairs(o, NULL);
}
}
// make sure we have at least one up stairs
assert(findobinmap(map, OT_STAIRSUP));
// DOWN STAIRS
if (map->depth < map->region->rtype->maxdepth) {
for (i = 0; i < map->region->rtype->stairsperlev; i++) {
c = NULL;
while (!c || !isempty(c) || countobs(c->obpile, B_TRUE)) {
c = getrandomroomcell(map, ANYROOM);
}
o = addobfast(c->obpile, OT_STAIRSDOWN);
linkstairs(o, NULL);
}
}
// add pillars & objects & monsters to rooms
if (wantrooms && (numrooms > 0)) {
for (i = 0; i < numrooms; i++) {
if (!roomvault[i] || hasflag(roomvault[i]->flags, F_AUTOPOPULATE)) {
int numobsmin,numobsmax,numobs,n;
int maxpillars;
//dblog("Adding obs to room %d/%d",i+1,numrooms);
maxpillars = (roomw[i] / 4) + (roomh[i] / 4);
// add pillars first
if ((maxpillars > 0) && pctchance(PCTCH_PILLAR)) {
int n;
int numpillars;
numpillars = rnd(1,maxpillars);
if (db) dblog("--> Will add %d pillars",numpillars);
for (n = 0; n < numpillars;n++ ) {
//dblog("----> Adding pillar %d/%d",n+1,numpillars);
cell_t *c;
c = getrandomroomcell(map, i);
if (c && isempty(c) && !countobs(c->obpile, B_TRUE)) {
setcelltype(cell, CT_WALL);
}
}
}
numobsmin = 0;
numobsmax = MAXOF(roomw[i],roomh[i]) / 2;
//numobsmax = MAXOF(roomw[i],roomh[i]);
// then objects/monsters
if (numobsmax <= numobsmin) {
numobs = numobsmin;
} else {
numobs = rnd(numobsmin,numobsmax);
}
if (db) dblog("--> Will add %d objects to room %d (of %d)",numobs,i,numrooms);
for (n = 0 ; n < numobs; n++) {
int ntries = 0;
int nmonsters = 0;
cell_t *c;
done = B_FALSE;
while (!done) {
c = getrandomroomcell(map, i);
// if nothing there
if (c && cellwalkable(NULL, c, NULL)) {
int obchance;
int nadded = 0;
// limit # monster per room to depth+1
if (nmonsters >= (depth+1)) {
obchance = 100;
} else {
// slightly more chance of objects in rooms
obchance = c->habitat->randobpct + 10;
}
if (addrandomthing(c,obchance, &nadded) == TT_MONSTER) {
nmonsters += nadded;
}
done = B_TRUE;
} else {
ntries++;
}
if (ntries >= numobs) {
done = B_TRUE;
break;
}
}
}
} // end if !vault
} // end foreach room
} // end if wantrooms & nrooms>0
if (db) dblog("Finished adding stuff to rooms.");
// now do a border
y = 0;
for (x = 0; x < map->w; x++) {
// n
c = getcellat(map, x, 0);
clearcell(c);
setcelltype(c,solidcell);
// s
c = getcellat(map, x, map->h-1);
clearcell(c);
setcelltype(c,solidcell);
}
for (y = 1; y < map->h-1; y++) {
// w
c = getcellat(map, 0, y);
clearcell(c);
setcelltype(c,solidcell);
// e
c = getcellat(map, map->w-1, y);
clearcell(c);
setcelltype(c,solidcell);
}
}
void createforest(map_t *map, int depth, map_t *parentmap, int exitdir, object_t *entryob, int nclearings) {
int x,y;
enum CELLTYPE emptycell;
int i;
int ntrees;
int density;
cell_t *c;
char buf[BUFLEN];
cell_t *retcell[MAXCANDIDATES];
int nretcells;
//object_t *o;
// fill entire maze with emptiness
emptycell = map->habitat->emptycelltype;
for (y = 0; y < map->h; y++) {
for (x = 0; x < map->w; x++) {
c = addcell(map, x, y);
setcelltype(c, emptycell);
}
}
switch (rnd(1,2)) {
case 1: // forest type 1: add trees in x% of cells, then add some clearings
// determine density
density = rnd(40,70);
// add a plant to density percent of cells
ntrees = (int)(((float)density/100.0) * (float)(map->w*map->h));
for (i = 0; i < ntrees; i++) {
c = getrandomcell(map);
while (c->lf) c = getrandomcell(map);
switch (rnd(0,1)) {
default: case 0: strcpy(buf, "tree"); break;
case 1: strcpy(buf, "shrub"); break;
}
addob(c->obpile, buf);
}
// clearings
for (i = 0; i < nclearings; i++) {
int w,n;
c = getrandomcell(map);
w = rnd(MINCLEARINGRADIUS,MAXCLEARINGRADIUS);
// clear obs in all clearing cells
getradiuscells(c, w, DT_ORTH, LOF_DONTNEED, B_TRUE, retcell, &nretcells, B_FALSE);
for (n = 0; n < nretcells; n++) {
// kill all obs here
while (retcell[n]->obpile->first) killob(retcell[n]->obpile->first);
}
// fill some cells with dirt
getradiuscells(c, w, DT_ORTH, LOF_DONTNEED, B_TRUE, retcell, &nretcells, 70);
for (n = 0; n < nretcells; n++) {
setcelltype(retcell[n], CT_DIRT);
}
}
break;
case 2: // add clusters of trees
nclearings = rnd(5,10);
for (i = 0; i < nclearings; i++) {
int w,n;
c = getrandomcell(map);
while (c->lf) c = getrandomcell(map);
w = rnd(MINCLEARINGRADIUS,MAXCLEARINGRADIUS);
getradiuscells(c, w, DT_ORTH, LOF_DONTNEED, B_TRUE, retcell, &nretcells, 80);
for (n = 0; n < nretcells; n++) {
switch (rnd(0,1)) {
default: case 0: strcpy(buf, "tree"); break;
case 1: strcpy(buf, "shrub"); break;
}
addob(retcell[n]->obpile, buf);
}
}
break;
}
}
void createhabitat(map_t *map, int depth, map_t *parentmap, int exitdir, object_t *entryob) {
switch (map->habitat->id) {
case H_DUNGEON:
createdungeon(map, depth, parentmap, exitdir, entryob);
break;
case H_FOREST:
createforest(map, depth, parentmap, exitdir, entryob, rnd(0,5));
break;
case H_VILLAGE:
createforest(map, depth, parentmap, exitdir, entryob, 0);
break;
case H_HEAVEN:
createheaven(map, depth, parentmap, exitdir, entryob);
break;
case H_PIT:
createpit(map, depth, parentmap, exitdir, entryob);
break;
default:
dblog("ERROR - createhabitat with invalid habitat!");
msg("ERROR - createhabitat with invalid habitat!");
}
}
void createheaven(map_t *map, int depth, map_t *parentmap, int exitdir, object_t *entryob) {
int x,y;
enum CELLTYPE emptycell;
cell_t *c;
race_t *r;
lifeform_t *lf;
//object_t *o;
// what kind of cells will 'empty' ones be?
emptycell = map->habitat->emptycelltype;
// fill entire maze with corridors
for (y = 0; y < map->h; y++) {
for (x = 0; x < map->w; x++) {
c = addcell(map, x, y);
setcelltype(c, emptycell);
}
}
// add one of each god.
for (r = firstrace ; r ; r = r->next) {
if (r->raceclass->id == RC_GOD) {
// find a position
c = getrandomcell(map);
while (!cellwalkable(NULL, c, NULL)) {
c = getrandomcell(map);
}
// place god
lf = addmonster(c, r->id, NULL, B_FALSE, 1, B_FALSE, NULL);
assert(lf);
// add to god list
godlf[ngodlfs++] = lf;
if (ngodlfs > MAXGODS) {
dblog("Error - number of gods(%d) exceeds MAXGODS.",ngodlfs);
msg("Error - number of gods(%d) exceeds MAXGODS.",ngodlfs);
exit(1);
}
}
}
}
/*
seed = random number seed
turnpct = percentage change of a corridor turning
sparseness = how many times to chop off dead ends
looppct = percentage change of turning dead-end into loop
maxrooms = max # of rooms
*/
void createmap(map_t *map, int depth, region_t *region, map_t *parentmap, int exitdir, object_t *entryob) {
lifeform_t *lf;
map_t *m;
char buf[BUFLEN],buf2[BUFLEN];
int i,x,y;
enum HABITAT habitat;
regionthing_t *thing[MAXOUTLINETHINGS];
int nthings = 0;
int db = B_FALSE;
// determine habitat based on region
// note: we might override this later based on our region outline
habitat = region->rtype->defaulthabitat;
/*
if ((region->rtype->id == RG_WORLDMAP) && (depth == 1)) {
firstworldmap = B_TRUE;
}
*/
map->beingcreated = B_TRUE;
map->depth = depth;
map->region = region;
if (db) {
getregionname(buf, map, B_FALSE);
dblog("Creating new map of region '%s'",buf);
}
map->habitat = findhabitat(habitat);
map->nrooms = 0;
// link to other maps if required.
// default to no links
for (i = D_N; i <= D_W; i++) {
map->nextmap[i] = -1;
}
for (i = D_UP; i <= D_DOWN; i++) {
map->nextmap[i] = -1;
}
// look for adjacent maps above/below this one
if (db) dblog(" look for adjacent maps above/below this one");
for (i = depth-1; i <= depth+1; i += 2) {
map_t *othermap;
othermap = findregionmap(map->region->id, i);
// TODO: set upmap/downmap for use later on.
if (othermap) {
if (i == (depth-1)) {
map->nextmap[D_UP] = othermap->id;
if (db) dblog(" found mapin dir d_up: %s", othermap->name);
} else {
map->nextmap[D_DOWN] = othermap->id;
if (db) dblog(" found mapin dir d_down: %s", othermap->name);
}
}
}
// did we come from a previous map in the same region?
if (parentmap) {
if ((parentmap->region->id == map->region->id) ||
(map->region->rtype->majorbranch)) {
if (db) dblog(" linking to parentmap %s in dir %s", parentmap->name, getdirname(diropposite(exitdir)));
parentmap->nextmap[exitdir] = map->id;
map->nextmap[diropposite(exitdir)] = parentmap->id;
}
}
/*
if (map->region->rtype->id == RG_WORLDMAP) {
map_t *adjmap;
for (i = D_N; i <= D_W; i++) {
// is there a map this dir from us???
adjmap = findmapcoords(, x, y);
if (i == diropposite(exitdir) ) {
map->nextmap[i] = parentmap->id;
} else {
map->nextmap[i] = -1;
}
}
}
*/
map->w = MAX_MAPW;
map->h = MAX_MAPH;
// map depth?
map->depth = depth;
// rememebr seed
map->seed = rand() % 65535;
srand(map->seed);
// set map coords
// first world map??
if (map == firstmap) {
if (db) dblog(" map is the first world map. setting coords to 0,0.");
addflag(map->flags, F_MAPCOORDS, 0, 0, NA, NULL);
x = 0;
y = 0;
} else {
// set mapcoords if not already done.
if (!hasflag(map->flags, F_MAPCOORDS)) {
x = -999;
y = -999;
// set mapcoords based on parent map
if (parentmap) {
getmapcoords(parentmap, &x, &y);
assert((x != -999) && (y != -999));
switch (exitdir) {
case D_N:
y--;
break;
case D_E:
x++;
break;
case D_S:
y++;
break;
case D_W:
x--;
break;
default:
// no change
break;
}
if (db) dblog(" setting map coords to %d,%d (based on parent map)",x,y);
} else {
// set it based on something else...
if (region->rtype->id == RG_WORLDMAP) {
// TODO: is this right???????????
// find another map of this region and set it.
for (m = firstmap ; m ; m = m->next) {
if ((m != map) && (m->region == region)) {
flag_t *ff;
ff = hasflag(m->flags, F_MAPCOORDS);
if (ff) {
if (db) dblog(" setting map coords to %d,%d (based on other region map)",x,y);
x = ff->val[0];
y = ff->val[1];
break;
}
}
}
} else {
x = 0;
y = 0;
}
}
assert(x != -999);
assert(y != -999);
addflag(map->flags, F_MAPCOORDS, x, y, NA, NULL);
}
}
// we now have the map name!
getregionname(buf2, map, B_TRUE);
snprintf(buf, BUFLEN, "%s (id #%d)",buf2, map->id);
map->name = strdup(buf);
// get a list of what things are here based on the region's outline
map->nfixedrooms = 0;
nthings = 0;
if (region->outline) {
if (db) dblog(" checking region outline for things...");
for (i = 0; i < region->outline->nthings; i++) {
int matched = B_FALSE;
if (db) {
dblog(" Checking outlinething #%d (thing:depth=%d,x=%d,y=%d thismap:depth=%d,x=%d,y=%d ).", i,
region->outline->thing[i].depth,
region->outline->thing[i].x,
region->outline->thing[i].y,
depth,x,y
);
}
if ((region->rtype->id == RG_WORLDMAP) &&
(region->outline->thing[i].depth == NA)) { // match on x/y coords
if ((region->outline->thing[i].x == x) && (region->outline->thing[i].y == y)) {
matched = B_TRUE;
}
} else { // match on depth
if (region->outline->thing[i].depth == getmapdifficulty(map)) {
matched = B_TRUE;
}
}
if (matched) {
// override region
if (region->outline->thing[i].whatkind == RT_HABITAT) {
habitat_t *h;
h = findhabitat(region->outline->thing[i].value);
if (db) dblog(" setting map habitat to %s based on outlinething.",h->name);
map->habitat = h;
} else {
if (db) dblog(" remembering region thing for later.");
// remember this thing
thing[nthings] = &region->outline->thing[i];
switch (thing[nthings]->whatkind) {
case RT_VAULT:
case RT_RNDVAULTWITHFLAG:
// this will reduce the amount of random rooms which can
// be created on this map.
map->nfixedrooms++;
break;
default:
break;
}
nthings++;
}
}
}
} else {
if (db) dblog(" region has no outline.");
}
if (db) dblog(" %d things remembered for later.",nthings);
// build it...
if (db) dblog(" creating map habitat.");
createhabitat(map, depth, parentmap, exitdir, entryob);
// add home objects
if (db) dblog(" adding home objects.");
for (lf = map->lf ; lf ; lf = lf->next) {
addhomeobs(lf);
}
// add outline things
if (db) dblog(" adding remembered region outline things...");
for (i = 0; i < nthings ;i++) {
vault_t *v;
// add this thing
switch (thing[i]->whatkind) {
case RT_HABITAT: // already handled above
break;
case RT_REGIONLINK:
if (db) dblog(" adding regionlink");
createregionlink(map, NULL, NULL, thing[i]->what, thing[i]->value, map->region);
// ... don't need to do this since we know there won't be anywhere to link to.
//linkstairs(o);
break;
case RT_VAULT:
if (db) dblog(" adding vault");
v = findvault(thing[i]->what);
assert(v);
if (createvault(map, map->nrooms, v, NULL, NULL, NULL, NULL)) {
dblog("ERROR - couldn't create vault %s on map %s", v->id, map->name);
}
break;
case RT_RNDVAULTWITHFLAG:
if (db) dblog(" adding rndvaultwithflag");
v = findvaultwithflag(thing[i]->value);
assert(v);
if (createvault(map, map->nrooms, v, NULL, NULL, NULL, NULL)) {
dblog("ERROR - couldn't create rndvaultwithflag %s on map %s", v->id, map->name);
}
break;
}
}
// ensure there are no unreachable areas
fix_reachability(map);
// special cases
// village - add town walls and clear it out
if (db) dblog(" finalising village creation...");
if (map->habitat->id == H_VILLAGE) {
int x1 = 999,y1 = 999,x2 = -1,y2 = -1,x,y;
int gx[MAXDIR_ORTH],gy[MAXDIR_ORTH];
int guardx[MAXDIR_ORTH][2], guardy[MAXDIR_ORTH][2];
int dir;
cell_t *c;
// find outermost dimensions of shops
for (y = 0; y < map->h; y++) {
for (x = 0; x < map->w; x++) {
c = getcellat(map, x, y);
if (c && isroom(c)) {
if (c->x < x1) x1 = c->x;
if (c->y < y1) y1 = c->y;
if (c->x > x2) x2 = c->x;
if (c->y > y2) y2 = c->y;
}
}
}
// extend walls a bit more
x1 -= rnd(2,4);
y1 -= rnd(2,4);
x2 += rnd(2,4);
y2 += rnd(2,4);
limit(&x1, 0, map->w-1);
limit(&x2, 0, map->w-1);
limit(&y1, 0, map->h-1);
limit(&y2, 0, map->h-1);
// decide where the gate will be (not the corner)
for (dir = D_N; dir <= D_W; dir++) {
switch (dir) {
case D_N:
gx[dir] = rnd(x1+2,x2-2);
gy[dir] = y1;
guardx[dir][0] = gx[dir]-1;
guardy[dir][0] = gy[dir]+1;
guardx[dir][1] = gx[dir]+1;
guardy[dir][1] = gy[dir]+1;
break;
case D_E:
gx[dir] = x2;
gy[dir] = rnd(y1+2,y2-2);
guardx[dir][0] = gx[dir]-1;
guardy[dir][0] = gy[dir]-1;
guardx[dir][1] = gx[dir]-1;
guardy[dir][1] = gy[dir]+1;
break;
case D_S:
gx[dir] = rnd(x1+2,x2-2);
gy[dir] = y2;
guardx[dir][0] = gx[dir]-1;
guardy[dir][0] = gy[dir]-1;
guardx[dir][1] = gx[dir]+1;
guardy[dir][1] = gy[dir]-1;
break;
case D_W:
gx[dir] = x1;
gy[dir] = rnd(y1+2,y2-2);
guardx[dir][0] = gx[dir]+1;
guardy[dir][0] = gy[dir]-1;
guardx[dir][1] = gx[dir]+1;
guardy[dir][1] = gy[dir]+1;
break;
}
}
// fill in town walls and clearing
for (y = 0; y <= map->h; y++) {
for (x = 0; x <= map->w; x++) {
c = getcellat(map, x, y);
if (c) {
if ((c->x >= x1) && (c->y >= y1) &&
(c->x <= x2) && (c->y <= y2)) {
// ie. within the village
if (!c->type->solid) {
int i;
// mark as different habitat to outside
c->habitat = findhabitat(H_VILLAGE);
if (!isroom(c)) {
// get rid of objects (ie. trees) here
killallobs(c->obpile);
// make the ground dirt
setcelltype(c, CT_DIRT);
}
if ((c->x == x1) || (c->y == y1) ||
(c->x == x2) || (c->y == y2)) {
// town perimeter (walls)
if (!isroom(c)) {
clearcell(c);
setcelltype(c, CT_DIRT);
addob(c->obpile, "wooden fence");
}
}
for (dir = D_N; dir <= D_W; dir++) {
// town gate location
if ((c->x == gx[dir]) && (c->y == gy[dir])) {
int d2;
cell_t *c2;
clearcell(c);
setcelltype(c, CT_DIRT);
addob(c->obpile, "wooden gate");
// also make surrounding forest cells be dirt and no trees,
for (d2 = DC_N; d2 <= DC_NW; d2++) {
c2 = getcellindir(c, d2);
if (c2 && (c2->habitat->id != H_VILLAGE)) {
clearcell(c2);
setcelltype(c2, CT_DIRT);
}
}
}
// village guards
for (i = 0; i < 1; i++) {
if ((c->x == guardx[dir][i]) && (c->y == guardy[dir][i])) {
addmonster(c, R_TOWNGUARD, NULL, B_FALSE, 1, B_TRUE, NULL);
}
}
}
}
} else {
// not within the village
c->habitat = findhabitat(H_FOREST);
}
} // end if c
}
}
}
// try to join up any unlinked staircases in this map.
if (db) dblog(" joining unlinked stairs...");
for (y = 0; y < map->h; y++) {
for (x = 0; x < map->w; x++) {
cell_t *c;
object_t *o;
c = getcellat(map, x, y);
o = hasobwithflag(c->obpile, F_CLIMBABLE);
if (o && !getstairdestination(o)) {
// this will join these stairs to existing ones on
// existing adjacent maps
if (!linkstairs(o, NULL)) {
if (db) {
cell_t *dst;
dst = getstairdestination(o);
dblog(" linked '%s' to map %s",o->type->name, dst->map->name);
}
} else {
if (db) {
dblog(" FAILED to link stiars: '%s'",o->type->name);
}
}
}
}
}
// link up holes - this will create NEW holes in THIS map connecting to
// EXISTING unlinked holes in adjacent maps
i = linkholes(map);
if (db) {
if (db) dblog(" autolinked to %d holes in adjacent maps.",i);
}
// add random objects and monsters
if (db) dblog(" adding random objects+monsters");
for (y = 0; y < map->h; y++) {
for (x = 0; x < map->w; x++) {
cell_t *c;
c = getcellat(map, x, y);
// no random obs in vaults
if (c && isempty(c) && !getcellvault(c)) {
if (rnd(1,100) <= c->habitat->randthingpct) {
addrandomthing(c, c->habitat->randobpct, NULL);
}
}
}
}
// look for adjacent maps
if (db) dblog(" linking to adjacent maps");
getmapcoords(map, &x, &y);
assert(x != -999);
assert(y != -999);
for (m = firstmap ; m ; m = m->next) {
if ((m != map) && (m->region == map->region)) {
int thisx,thisy;
getmapcoords(m, &thisx, &thisy);
if (map->nextmap[D_N] == -1) {
if ((thisy == (y - 1)) && (thisx == x)) {
map->nextmap[D_N] = m->id;
if (db) dblog(" linked to map %d (dir N)",m->id);
}
}
if (map->nextmap[D_E] == -1) {
if ((thisx == (x + 1)) && (thisy == y)) {
map->nextmap[D_E] = m->id;
if (db) dblog(" linked to map %d (dir E)",m->id);
}
}
if (map->nextmap[D_S] == -1) {
if ((thisy == (y + 1)) && (thisx == x)) {
map->nextmap[D_S] = m->id;
if (db) dblog(" linked to map %d (dir S)",m->id);
}
}
if (map->nextmap[D_W] == -1) {
if ((thisx == (x - 1)) && (thisy == y)) {
map->nextmap[D_W] = m->id;
if (db) dblog(" linked to map %d (dir W)",m->id);
}
}
}
}
map->beingcreated = B_FALSE;
if (db) dblog(" Map creation finished.");
}
int createvault(map_t *map, int roomid, vault_t *v, int *retw, int *reth, int *retx, int *rety) {
int w,h,x,y;
int xmargin = DEF_VAULTMARGIN,ymargin = DEF_VAULTMARGIN; // defaults
int minx,miny,maxx,maxy;
int db = B_FALSE;
int rotation = 0;
room_t *thisroom = NULL;
flag_t *f;
// default
if (retw) *retw = -1;
if (reth) *reth = -1;
if (!v) {
return B_TRUE;
}
f = hasflag(v->flags, F_KEEPMARGIN);
if (f) {
xmargin = f->val[0];
ymargin = f->val[1];
}
// handle rotation.
rotation = rnd(0,v->nmaps-1);
f = hasflag(v->flags, F_VAULTRANDOMMAP);
if (f) {
int minw,minh;
// just make a normal room to start with. but make sure :
// - it is surrounded with walls!
// - we don't do autodoors (will handle this further down) -------------
minw = f->val[0];
minh = f->val[1];
if (createroom(map, roomid, minw, minh, xmargin, ymargin, &minx, &miny, &w, &h, B_NODOORS, B_TRUE)) {
return B_TRUE;
}
if (db) dblog("made random vault %s at pos %d,%d on map %s", v->id, minx, miny, map->name);
maxx = minx + w - 1;
maxy = miny + h - 1;
} else {
// get width/height from vault
//w = v->w;
//h = v->h;
getvaultwh(v, &w, &h, rotation);
// find vault position
if (calcroompos(map, w, h, xmargin, ymargin, &minx, &miny, B_TRUE)) {
// forced calcroompos should never fail since it's
// allowed to overlap other rooms.
dblog("** couldn't make vault room!\n");
//msg("** ALERT: couldn't make vault room!\n");
return B_TRUE;
}
maxx = minx + (w-1);
maxy = miny + (h-1);
map->room[map->nrooms].id = roomid;
map->room[map->nrooms].x1 = minx;
map->room[map->nrooms].y1 = miny;
map->room[map->nrooms].x2 = maxx;
map->room[map->nrooms].y2 = maxy;
map->room[map->nrooms].vault = v;
map->room[map->nrooms].exitslinked = B_FALSE;
thisroom = &(map->room[map->nrooms]);
map->nrooms++;
// now make it
if (db) dblog("making vault %s at pos %d,%d on map %s", v->id, minx, miny, map->name);
for (y = miny; y <= maxy; y++) {
for (x = minx; x <= maxx; x++) {
cell_t *cell;
celltype_t *ct;
cell = getcellat(map, x, y);
clearcell(cell);
// set cell type
ct = getvaultcelltype(v, x-minx,y-miny, rotation);
setcelltype(cell, ct ? ct->id : cell->habitat->emptycelltype);
// set roomid
cell->room = thisroom;
// add objects
addvaultcellcontents(cell, v, x-minx,y-miny, rotation);
}
}
}
if (retw) *retw = w;
if (reth) *reth = h;
if (retx) *retx = minx;
if (rety) *rety = miny;
// add other stuff to the vault based on flags
// this will also set cell->vault for all cells.
addvaultcontents(map, v, minx, miny, maxx, maxy, rotation);
// auto add doors if required
f = hasflag(v->flags, F_AUTODOORS);
if (f) {
// when adding autodoors to a vault, they are ALWAYS closed.
autodoors(map, roomid, minx, miny, maxx, maxy, f->val[0], B_NODOORS);
}
// link up exits from this vault
linkexits(map, roomid);
return B_FALSE;
}
// link a single cell up to the rest of the map.
// make sure it links to an empty cell of a DIFFERENT roomid.
// if 'wantfilled' is set, only link to "filled" cells.
// return TRUE on failure.
int linkexit(cell_t *startcell, int wantfilled, int *ncellsadded) {
int db = B_TRUE;
int d, roomid;
int poss2[MAXCANDIDATES],nposs2;
int dist[MAXDIR_ORTH],hitsedge[MAXDIR_ORTH], sameroom[MAXDIR_ORTH];
cell_t *directendcell[MAXDIR_ORTH];
int mindist = 999,maxdist = -1;
cell_t *c;
if (ncellsadded) *ncellsadded = 0;
if (db) dblog(" calling linkexit() for cell at %d,%d", startcell->x, startcell->y);
roomid = getroomid(startcell);
for (d = D_N; d <= D_W; d++) {
hitsedge[d] = B_TRUE;
directendcell[d] = NULL;
}
// link it. starting from the door, count the number of cells in
// each direction until we hit an empty (walkable) cell which isn't a room.
// if we hit a cell of this roomid, mark this dir as invalid.
for (d = D_N; d <= D_W; d++) {
dist[d] = 0;
hitsedge[d] = B_FALSE;
sameroom[d] = B_FALSE;
c = getcellindir(startcell, d);
while (c) {
dist[d]++;
if ((roomid >= 0) && getroomid(c) == roomid) { // same room
if (wantfilled && c->type->solid) {
// EXCEPTION:
// if we are calling this function from fix_reachability, then
// startcell will actually be a cell _inside_ the room as opposed to
// a cell inside the room's walls.
// in this case, we ARE allowed to travel through the room's walls.
} else {
// mark dir as invalid
dist[d] = 999;
sameroom[d] = B_TRUE;
if (db) dblog(" going %s hits same room. invalid.", getdirname(d));
break;
}
} else if (cellwalkable(NULL, c, NULL)) {
if (!wantfilled || c->filled) {
// walkable and not in this vault. finished.
directendcell[d] = c;
if (db) dblog(" can make %s path (hits empty cell at dist %d)", getdirname(d), dist[d]);
break;
}
} else {
int perpdir[2],n;
cell_t *pcell = NULL;
perpdir[0] = d - 1; if (perpdir[0] < D_N) perpdir[0] = D_W;
perpdir[1] = d + 1; if (perpdir[1] > D_W) perpdir[1] = D_N;
// is there an adjacent walkable cell in a perpendicular direction
// which isn't from the starting room?
for (n = 0; n <= 1; n++) {
pcell = getcellindir(c, perpdir[n]);
if (pcell) {
if (((roomid == -1 ) || (getroomid(pcell) != roomid)) &&
cellwalkable(NULL, pcell, NULL)) {
if (!wantfilled || c->filled) {
// finished.
directendcell[d] = c;
if (db) dblog(" can make %s path (hits adjacent empty cell at dist %d)", getdirname(d), dist[d]);
break;
}
}
}
}
}
// check next cell
c = getcellindir(c, d); // getting the same cell!
}
if (!c) {
if (db) dblog(" going %s hits edge of map.", getdirname(d));
hitsedge[d] = B_TRUE;
}
if (dist[d] != 999) {
if (!hitsedge[d]) {
if (dist[d] < mindist) mindist = dist[d];
}
if (dist[d] > maxdist) maxdist = dist[d];
}
}
if (mindist == 999) {
cell_t *turncell = NULL,*endcell = NULL;
cell_t *perpcell[MAX_MAPW*MAX_MAPH];
cell_t *perpturncell1[MAX_MAPW*MAX_MAPH];
int perpturndir1[MAX_MAPW*MAX_MAPH];
int nperpcells = 0;
int perpdir[2];
int startdir = D_NONE;
int turndir = D_NONE;
int startdist = 0;
int maxdist2 = -1;
int startposs[MAXDIR_ORTH];
int nstartposs = 0;
// no good directions.
if (db) dblog(" No directions lead to valid cells. Trying turns.");
// starting at the LONGEST distance, traverse up each dir,
// branching off looking for rooms.
// find longest distance that doesn't go through same room
for (d = D_N; d <= D_W; d++) {
if (!sameroom[d] && (dist[d] > maxdist2)) {
maxdist2 = dist[d];
}
}
// pick one randomly
for (d = D_N; d <= D_W; d++) {
if (dist[d] == maxdist2) {
startposs[nstartposs++] = d;
}
}
if (nstartposs) {
startdir = startposs[rnd(0,nstartposs-1)];
}
assert(startdir != D_NONE);
// figure out perpendicular dirs
perpdir[0] = startdir - 1; if (perpdir[0] < D_N) perpdir[0] = D_W;
perpdir[1] = startdir + 1; if (perpdir[1] > D_W) perpdir[1] = D_N;
if (db) dblog(" Will walk %s (dist %d), checking %s and %s.", getdirname(startdir), maxdist,
getdirname(perpdir[0]), getdirname(perpdir[1]));
// check in startdir
c = getcellindir(startcell, startdir);
while (c && !turncell) {
int n;
cell_t *c2;
startdist++;
// check left/right from this door for rooms
for (n = 0; n <= 1; n++) {
int turndist = 0;
c2 = getcellindir(c, perpdir[n]);
while (c2) {
int gotsolution = B_FALSE;
turndist++;
perpcell[nperpcells] = c2; // this will be used if we need to make 2 turns
perpturncell1[nperpcells] = c;
perpturndir1[nperpcells] = perpdir[n];
nperpcells++;
if ((roomid >= 0) && (getroomid(c2) == roomid)) {
if (wantfilled && c2->type->solid) {
// see EXCEPTION above.
} else {
// hits same room, not ok.
break;
}
} else if (cellwalkable(NULL, c2, NULL)) {
if (!wantfilled || c2->filled) {
if (db) dblog(" Got to an empty cell here.");
gotsolution = B_TRUE;
}
} else if (turndist > 1) {
// check l/r too
int perpdir2[2],nn;
cell_t *pcell = NULL;
perpdir2[0] = perpdir[n] - 1; if (perpdir2[0] < D_N) perpdir2[0] = D_W;
perpdir2[1] = perpdir[n] + 1; if (perpdir2[1] > D_W) perpdir2[1] = D_N;
for (nn = 0; nn <= 1; nn++) {
pcell = getcellindir(c2, perpdir2[nn]);
if (pcell) {
if ( ((roomid == -1) || (getroomid(pcell) != roomid)) &&
cellwalkable(NULL, pcell, NULL)) {
if (!wantfilled || pcell->filled) {
// finished.
if (db) dblog(" Got to an empty cell next to us.");
gotsolution = B_TRUE;
break;
}
}
}
}
}
if (gotsolution) {
if (db) dblog(" Solution found: Walk %d %s, then %d %s.",
startdist, getdirname(startdir),
turndist, getdirname(perpdir[n]));
// walkable and not in this roomid. ok!
turncell = c;
endcell = c2;
turndir = perpdir[n];
break;
}
// check next cell
c2 = getcellindir(c2, perpdir[n]);
}
if (turncell) break;
}
// now keep going in main direction.
c = getcellindir(c, startdir);
}
if (turncell) {
// make a path up to the turn point.
if (db) dblog(" Making path from vault to corner, initdir=%s", getdirname(startdir));
c = getcellindir(startcell, startdir);
while (c != turncell) {
setcelltype(c, c->habitat->emptycelltype);
if (ncellsadded) (*ncellsadded)++;
c = getcellindir(c, startdir);
}
// clear the corner cell
setcelltype(c, c->habitat->emptycelltype);
if (db) dblog(" Making path from corner to rest of map, turndir=%s", getdirname(turndir));
// now turn and clear up to the next room/empty cell
c = getcellindir(c, turndir);
while (c != endcell) {
setcelltype(c, c->habitat->emptycelltype);
if (ncellsadded) (*ncellsadded)++;
c = getcellindir(c, turndir);
}
} else {
// We need to make 2 turns.
// for each perpcell[], look in startdir + diropposite(startdir)
int dir3[2],i,n;
cell_t *turncell2 = NULL;
int turndir2;
dir3[0] = startdir;
dir3[1] = diropposite(startdir);
if (db) dblog(" Need to make two turns. Searching %s and %s from each perpcell.", getdirname(dir3[0]),
getdirname(dir3[1]));
for (i = 0; i < nperpcells; i++) {
for (n = 0; n < 2; n++) {
cell_t *c2;
int turndist = 0;
if (db) dblog_nocr("looking %s from %d,%d: ", getdirname(dir3[n]),
perpcell[i]->x, perpcell[i]->y);
c2 = getcellindir(perpcell[i], dir3[n]);
while (c2) {
int gotsolution = B_FALSE;
turndist++;
if (db) dblog_nocr("(%d,%d)",c2->x,c2->y);
if ((roomid >= 0) && (getroomid(c2) == roomid)) {
if (wantfilled && c2->type->solid) {
// see EXCEPTION above.
} else {
// hits same room, not ok.
break;
}
} else if (cellwalkable(NULL, c2, NULL)) {
if (!wantfilled || c2->filled) {
if (db) dblog(" Got to an empty cell here.");
gotsolution = B_TRUE;
}
} else if (turndist > 1) {
// check l/r too
int perpdir2[2],nn;
cell_t *pcell = NULL;
perpdir2[0] = perpdir[n] - 1; if (perpdir2[0] < D_N) perpdir2[0] = D_W;
perpdir2[1] = perpdir[n] + 1; if (perpdir2[1] > D_W) perpdir2[1] = D_N;
for (nn = 0; nn <= 1; nn++) {
pcell = getcellindir(c2, perpdir2[nn]);
if (pcell) {
if ( ((roomid == -1) || (getroomid(pcell) != roomid)) &&
cellwalkable(NULL, pcell, NULL)) {
if (!wantfilled || pcell->filled) {
// finished.
if (db) dblog(" Got to an empty cell next to us.");
gotsolution = B_TRUE;
break;
}
}
}
}
}
if (gotsolution) {
turncell = perpturncell1[i];
turndir = perpturndir1[i];
turncell2 = perpcell[i];
turndir2 = dir3[n];
endcell = c2;
break;
}
// check next cell
c2 = getcellindir(c2, dir3[n]);
} // end while c2
if (turncell2) break;
if (db) dblog("");
} // end for n=1-2
if (turncell2) break;
} // end foreach perpcell
// TODO: if we find a solution, fill in turncell2 and make path.
if (turncell2) {
if (db) dblog(" Twoturn solution found: Walk %s, then %s, then %s.",
getdirname(startdir), getdirname(turndir), getdirname(turndir2));
// make a path up to the turn point.
if (db) dblog(" Making path from vault to first corner, initdir=%s", getdirname(startdir));
c = getcellindir(startcell, startdir);
while (c != turncell) {
setcelltype(c, c->habitat->emptycelltype);
if (ncellsadded) (*ncellsadded)++;
c = getcellindir(c, startdir);
}
// clear the corner cell
setcelltype(c, c->habitat->emptycelltype);
// now turn and clear up to the next turn
if (db) dblog(" Making path from 1st corner to 2nd corner, turndir=%s", getdirname(turndir));
c = getcellindir(c, turndir);
while (c != turncell2) {
setcelltype(c, c->habitat->emptycelltype);
if (ncellsadded) (*ncellsadded)++;
c = getcellindir(c, turndir);
}
// now turn and clear up to the next room/empty cell
if (db) dblog(" Making path from 2nd corner to rest of map, turndir=%s", getdirname(turndir2));
c = getcellindir(c, turndir2);
while (c != endcell) {
setcelltype(c, c->habitat->emptycelltype);
if (ncellsadded) (*ncellsadded)++;
c = getcellindir(c, turndir2);
}
} else {
if (db) dblog(" Cannot find a way to link up.");
assert(0 == 1); // for debugging
}
return B_TRUE;
}
} else { // we found a way to go without needing to turn.
int whichway,sel;
int mindist2 = 999;
// now pick the shortest one which doesn't hit the edge
// get list of all the minimums and randomly tie-break
nposs2 = 0;
for (d = D_N; d <= D_W; d++) {
if (!hitsedge[d] && (dist[d] < mindist2)) {
mindist2 = dist[d];
}
}
for (d = D_N; d <= D_W; d++) {
if (dist[d] == mindist2) {
poss2[nposs2++] = d;
}
}
sel = rnd(0,nposs2-1);
whichway = poss2[sel];
// now create a path
if (db) dblog(" Linking directly %s (distance %d).", getdirname(whichway), mindist);
c = getcellindir(startcell, whichway);
//while (c && !cellwalkable(NULL, c, NULL)) {
while (c && c != directendcell[whichway]) {
setcelltype(c, c->habitat->emptycelltype);
if (ncellsadded) (*ncellsadded)++;
c = getcellindir(c, whichway);
}
}
return B_FALSE;
}
// make sure exits/doors in a given room link up to the rest of the map.
int linkexits(map_t *m, int roomid) {
int x,y,i;
cell_t *poss[MAXCANDIDATES],*c;
int nposs = 0;
int db = B_TRUE;
int nadded = 0;
int minx = -1, miny = -1, maxx = -1, maxy = -1;
int roomidx = -1;
// figure out room coords
for (i = 0; i < m->nrooms; i++) {
if (m->room[i].id == roomid) {
minx = m->room[i].x1;
miny = m->room[i].y1;
maxx = m->room[i].x2;
maxy = m->room[i].y2;
roomidx = i;
break;
}
}
assert(roomidx != -1);
if (db) {
char buf[BUFLEN];
vault_t *v;
c = getrandomroomcell(m, roomid);
v = getcellvault(c);
snprintf(buf, BUFLEN, "*** linkexits for roomid %d (%s) [%d,%d-%d,%d]", roomid,
v ? v->id : "novault",
minx,miny,maxx,maxy);
dblog("%s", buf);
}
// find all doors or f_roomexits
for (y = miny; y <= maxy; y++) {
for (x = minx; x <= maxx; x++) {
c = getcellat(m, x, y);
if (!c) continue;
if (hasobwithflag(c->obpile, F_DOOR)) {
if ((x == minx) || (x == maxx) || (y == miny) || (y == maxy) ) {
if (db) dblog("found wall door at %d,%d",x,y);
poss[nposs++] = c;
}
} else if (hasflagval(m->flags, F_ROOMEXIT, roomid, x, y, NULL)) {
if (db) dblog("found roomexit at %d,%d",x,y);
poss[nposs++] = c;
}
}
}
if (db) dblog("%d doors/exits found.",nposs);
// for each door, make sure it links to at least one cell which isn't
// part of this room
for (i = 0; i < nposs; i++) {
int ncorridors = 0,d;
// if exit is solid and COMPLETELY surrounded by solid, ignore it.
if (c->type->solid && (countcellexits(c) == 0)){
if (db) dblog("cell is solid and surrounded by solids. ignoring.");
continue;
}
if (db) dblog("linking exit #%d",i);
for (d = D_N; d <= D_W; d++) {
c = getcellindir(poss[i], d);
if (c && cellwalkable(NULL, c, NULL) && (getroomid(c) != roomid)) {
ncorridors++;
}
}
if (db) dblog(" This exit leads to %d corridors. ", ncorridors);
// no corridors?
if (ncorridors == 0) {
if (db) dblog(" Need to link.");
nadded += linkexit(poss[i], B_FALSE, NULL);
}
} // end for each door
m->room[roomidx].exitslinked = B_TRUE;
if (db) dblog("linkexits complete (%d added).", nadded);
return nadded;
}
void createpit(map_t *map, int depth, map_t *parentmap, int exitdir, object_t *entryob) {
object_t *o;
cell_t *c;
int x,y;
// fill entire maze with walls
for (y = 0; y < map->h; y++) {
for (x = 0; x < map->w; x++) {
addcell(map, x, y);
}
}
// select random spot
c = getrandomcell(map);
// clear it
setcelltype(c, CT_CORRIDOR);
// put an exit here
o = addobject(c->obpile, NULL, B_FALSE, B_FALSE, OT_HOLEINROOF);
assert(o);
// link it
linkstairs(o, entryob);
}
// only need to provide either obname _OR_ o
void createregionlink(map_t *m, cell_t *c, object_t *o, char *obname, enum REGIONTYPE newregiontype, region_t *parent) {
flag_t *f;
region_t *r;
// create a new region
r = addregion(newregiontype, m->region, -1);
// add stairs to new region
if (!c) {
c = NULL;
while (!c || !cellwalkable(NULL, c, NULL)) {
c = getrandomcell(m);
}
}
// create object if requested
if (!o) {
o = addob(c->obpile, obname);
assert(o);
}
// link stairs to the new region
f = hasflag(o->flags, F_CLIMBABLE);
assert(f);
f->val[1] = r->id;
}
// room w/h are returned in *w and *h if given.
int createroom(map_t *map, int roomid, int overrideminw, int overrideminh, int xmargin, int ymargin, int *retx, int *rety, int *retw, int *reth, int doorpct, int forcewalls) {
int x,y;
cell_t *cell;
int minx,miny;
int maxx,maxy;
int w,h;
int minroomw = MIN_ROOMW;
int minroomh = MIN_ROOMH;
int maxroomw = MAX_ROOMW;
int maxroomh = MAX_ROOMH;
room_t *thisroom = NULL;
if (overrideminw != NA) {
minroomw = overrideminw;
}
if (overrideminh != NA) {
minroomh = overrideminh;
}
// select random width/height
w = rnd(minroomw, maxroomw);
h = rnd(minroomh, maxroomh);
if (retw) *retw = w;
if (reth) *reth = h;
// find room position
if (calcroompos(map, w, h, xmargin, ymargin, &minx, &miny, B_FALSE)) {
dblog("** couldn't make room!\n");
return B_TRUE;
}
if (retx) *retx = minx;
if (rety) *rety = miny;
// we now have the room position - fill it in
//printf("trying to create room at (%d,%d) w=%d,h=%d\n", minx, miny, w, h);
maxx = minx + (w-1);
maxy = miny + (h-1);
map->room[map->nrooms].id = roomid;
map->room[map->nrooms].x1 = minx;
map->room[map->nrooms].y1 = miny;
map->room[map->nrooms].x2 = maxx;
map->room[map->nrooms].y2 = maxy;
map->room[map->nrooms].vault = NULL;
map->room[map->nrooms].exitslinked = B_FALSE;
thisroom = &(map->room[map->nrooms]);
map->nrooms++;
for (y = miny; y <= maxy; y++) {
for (x = minx; x <= maxx; x++) {
cell = getcellat(map, x, y);
if (cell) {
// kill contents, EXCEPT for staircases!
clearcell_exceptflags(cell, F_CLIMBABLE, F_NONE);
// make it a border or room
if ((y == miny) || (y == maxy) ||
(x == minx) || (x == maxx)) {
// ie. if you haven't forced walls then if this room overlaps
// with another one, no walls will be created.
if (forcewalls || (!forcewalls && cell->type->solid)) {
setcelltype(cell, CT_ROOMWALL);
}
//}
} else {
setcelltype(cell, CT_ROOM);
}
cell->room = thisroom;
}
}
}
// add doors
if (doorpct) {
autodoors(map, roomid, minx, miny, maxx, maxy, doorpct, 50);
}
return B_FALSE;
}
int dirtox(int dt, int dir) {
if (dt == DT_ORTH) {
switch (dir) {
case D_E:
return 1;
case D_W:
return -1;
default:
return 0;
}
} else if (dt == DT_COMPASS) {
switch (dir) {
case DC_NE:
case DC_E:
case DC_SE:
return 1;
case DC_NW:
case DC_W:
case DC_SW:
return -1;
default:
return 0;
}
}
return 0;
}
int dirtoy(int dt, int dir) {
if (dt == DT_ORTH) {
switch (dir) {
case D_S:
return 1;
case D_N:
return -1;
default:
return 0;
}
} else if (dt == DT_COMPASS) {
switch (dir) {
case DC_SW:
case DC_S:
case DC_SE:
return 1;
case DC_NE:
case DC_N:
case DC_NW:
return -1;
default:
return 0;
}
}
return 0;
}
void dumpmap(map_t *map) {
int x,y;
cell_t *cell;
char ch;
printf("dump of map '%s' (%d x %d):\n",map->name, map->w, map->h);
// top reference row
dblog_nocr(" ");
for (x = 0; x < map->w; x++) {
dblog_nocr("%d",x % 10);
}
dblog_nocr("\n");
for (y = 0; y < map->h; y++) {
dblog_nocr("%d",y % 10);
for (x = 0; x < map->w; x++) {
cell = getcellat(map, x, y);
ch = cell->type->glyph.ch;
if ((ch == '.') && cell->filled) {
ch = 'o';
}
dblog_nocr("%c",ch);
}
dblog_nocr("\n");
}
}
void dumpoutlines(void) {
region_t *r;
int i;
for (r = firstregion ; r ; r = r->next) {
dblog("region:%s",r->rtype->name);
if (r->outline) {
for (i = 0; i < r->outline->nthings; i++ ){
regionthing_t *rt;
char loctext[BUFLEN];
rt = &r->outline->thing[i];
if (rt->depth == NA) {
snprintf(loctext, BUFLEN, "%d,%d",rt->x, rt->y);
} else {
snprintf(loctext, BUFLEN, "depth %d",rt->depth);
}
if (rt->whatkind == RT_REGIONLINK) {
regiontype_t *rtype;
rtype = findregiontype(rt->value);
dblog(" at %s: link to %s",loctext, rtype->name);
} else if (rt->whatkind == RT_HABITAT) {
habitat_t *h;
h = findhabitat(rt->value);
dblog(" at %s: %s",loctext, h->name);
} else if (rt->whatkind == RT_VAULT) {
vault_t *v;
v = findvault(rt->what);
dblog(" at %s: %s",loctext, v->id);
}
}
}
}
}
// dirtype of DT_ORTH will give a square explosion
// dirtype of DT_COMPASS will give a circular explosion
void explodecells(cell_t *c, int dam, int killwalls, object_t *o, int range, int dirtype, int wantannounce) {
int x,y;
if (dirtype == DT_COMPASS) {
animradial(c, range, '}', C_RED);
} else { // ie. DT_ORTH
animradialorth(c, range, '}', C_RED);
}
if (haslos(player, c)) {
if (wantannounce) {
msg("You see %s explosion!", (range > 0) ? "a huge" : "an");
}
} else {
noise(c, NULL, NC_OTHER, (range > 0) ? 6 : 5, "an explosion!", NULL);
}
for (y = c->y - range ; y <= c->y + range ; y++) {
for (x = c->x - range ; x <= c->x + range ; x++) {
int inrange = B_FALSE;
cell_t *cc;
cc = getcellat(c->map, x,y);
if (cc) {
if ((dirtype == DT_COMPASS) && (getcelldist(c,cc) <= range)) {
inrange = B_TRUE;
} else if ((dirtype == DT_ORTH) && (getcelldistorth(c,cc) <= range)) {
inrange = B_TRUE;
}
if (inrange && haslof(c, cc, LOF_WALLSTOP, NULL)) {
explodesinglecell(cc, dam, killwalls, o, c);
}
}
}
}
// lfs up to 1 cell away are knocked back
for (y = c->y - range-1 ; y <= c->y + range+1 ; y++) {
for (x = c->x - range-1 ; x <= c->x + range+1 ; x++) {
cell_t *cc;
int mydist;
cc = getcellat(c->map, x,y);
mydist = getcelldist(c,cc);
if (cc && (mydist <= (range+1))) {
if (cc->lf && !isdead(cc->lf)) {
// move away from centre of explosion
knockback(cc->lf, getdiraway(cc, c, NULL, B_FALSE, DT_COMPASS, B_FALSE), 2, NULL, 40-(mydist*10));
}
}
}
}
}
// this should never be called directly - only from explodecells().
// (otherwise knockback effect won't happen)
void explodesinglecell(cell_t *c, int dam, int killwalls, object_t *o, cell_t *centre) {
char obname[BUFLEN];
if (c->lf) {
char buf[BUFLEN];
if (o) {
getobname(o, obname, 1);
snprintf(buf, BUFLEN, "an exploding %s",strchr(obname, ' ')+1);
} else {
snprintf(buf, BUFLEN, "an explosion");
}
// take damage
losehp(c->lf, dam, DT_EXPLOSIVE, NULL, buf);
}
damageallobs(o, c->obpile, dam, DT_EXPLOSIVE);
if (killwalls) {
if (c->type->solid) {
// make it empty!
setcelltype(c, c->habitat->emptycelltype);
// add some rubble
addob(c->obpile, "10-50 stones");
}
}
}
celltype_t *findcelltype(enum CELLTYPE cid) {
celltype_t *ct;
for (ct = firstcelltype ; ct ; ct = ct->next) {
if (ct->id == cid) return ct;
}
return NULL;
}
celltype_t *findcelltypebyname(char *name) {
celltype_t *ct;
for (ct = firstcelltype ; ct ; ct = ct->next) {
if (!strcmp(ct->name, name)) return ct;
}
return NULL;
}
habitat_t *findhabitat(enum HABITAT id) {
habitat_t *h;
for (h = firsthabitat ; h ; h = h->next) {
if (h->id == id) return h;
}
return NULL;
}
habitat_t *findhabitatbyname(char *name) {
habitat_t *h;
for (h = firsthabitat ; h ; h = h->next) {
if (streq(h->name, name)) return h;
}
return NULL;
}
map_t *findmap(int mid) {
map_t *m;
for (m = firstmap ; m ; m = m->next) {
if (m->id == mid) return m;
}
return NULL;
}
map_t *findmapofdepth(int depth) {
map_t *m;
for (m = firstmap ; m ; m = m->next) {
if (m->depth == depth) return m;
}
return NULL;
}
cell_t *findmapentrypoint(map_t *m, int side, lifeform_t *lf) {
int x,y,xinc,yinc;
cell_t *selection = NULL;
cell_t *bestcell = NULL;
int closest = 999;
switch (side) {
case D_N:
case DC_N:
x = 0;
y = 0;
xinc = 1;
yinc = 0;
bestcell = getcellat(m, lf->cell->x, 0);
break;
case D_E:
case DC_E:
x = m->w-1;
y = 0;
xinc = 0;
yinc = 1;
bestcell = getcellat(m, m->w - 1, lf->cell->y);
break;
case D_S:
case DC_S:
x = 0;
y = m->h - 1;
xinc = 1;
yinc = 0;
bestcell = getcellat(m, lf->cell->x, m->h - 1);
break;
case D_W:
case DC_W:
x = 0;
y = 0;
xinc = 0;
yinc = 1;
bestcell = getcellat(m, 0, lf->cell->y);
break;
default:
dblog("error: invalid side given to mapentrypoint.");
msg("error: invalid side given to mapentrypoint.");
return NULL;
}
for (;(x < m->w) && (y < m->h);) {
cell_t *c;
// check cell
c = getcellat(m, x, y);
if (c && cellwalkable(lf, c, NULL)) {
int dist;
dist = getcelldist(c, bestcell);
if (dist < closest) {
selection = c;
closest = dist;
}
}
// go to next one
x += xinc;
y += yinc;
}
return selection;
}
// find the object with id 'id' in map 'm'
object_t *findobidinmap(map_t *m, long id) {
cell_t *c;
int x,y;
for (y = 0; y < m->h; y++) {
for (x = 0; x < m->w; x++) {
object_t *o;
c = getcellat(m, x, y);
o = findobbyid(c->obpile, id);
if (o) return o;
}
}
return NULL;
}
// find the cell in 'map' which contains object oid
cell_t *findobinmap(map_t *m, enum OBTYPE oid) {
cell_t *c;
int x,y;
for (y = 0; y < m->h; y++) {
for (x = 0; x < m->w; x++) {
c = getcellat(m, x, y);
if (hasob(c->obpile, oid)) {
return c;
}
}
}
return NULL;
}
regionoutline_t *findoutline(int id) {
regionoutline_t *ro;
for (ro = firstregionoutline ;ro ; ro = ro->next) {
if (ro->id == id) return ro;
}
return NULL;
}
region_t *findregion(int regionid) {
region_t *r;
for (r = firstregion ; r ; r = r->next) {
if (r->id == regionid) return r;
}
return NULL;
}
// this will objviously only work for unique regiontypes like
// rg_worldmap and rg_firstdungeon
region_t *findregionbytype(enum REGIONTYPE rtid) {
region_t *r;
for (r = firstregion ; r ; r = r->next) {
if (r->rtype->id == rtid) return r;
}
return NULL;
}
map_t *findregionmap(int regionid, int depth) {
map_t *m;
for (m = firstmap ; m ; m = m->next) {
if ((m->depth == depth) && (m->region->id == regionid)) return m;
}
return NULL;
}
regiontype_t *findregiontype(enum REGIONTYPE rtype) {
regiontype_t *rt;
for (rt = firstregiontype ; rt ; rt = rt->next) {
if (rt->id == rtype) return rt;
}
return NULL;
}
room_t *findroom(map_t *m, int roomid) {
int i;
for (i = 0; i < m->nrooms; i++) {
if (m->room[i].id == roomid) {
return &(m->room[i]);
}
}
return NULL;
}
map_t *findsurfaceexitmap(map_t *m) {
object_t *o;
cell_t *c;
int x,y;
// find up stairs on current map
for (y = 0; y < m->h; y++) {
for (x = 0; x < m->w; x++) {
c = getcellat(m, x, y);
if (c) {
for (o = c->obpile->first ; o ; o = o->next) {
if (hasflagval(o->flags, F_CLIMBABLE, D_UP, NA, NA, NULL)) {
cell_t *newc;
newc = getstairdestination(o);
if (newc) {
return newc->map;
}
}
}
}
}
}
return NULL;
}
void forgetcells(map_t *map, int amt) {
int amtleft;
//int totcells;
int i;
cell_t *poss[MAX_MAPW*MAX_MAPH];
cell_t *c;
int nposs = 0;
// how many cells to forget?
//totcells = (map->w * map->h);
//amtleft = (int) (((float) pct / 100.0) * (float)totcells);
amtleft = amt;
// get a list of all known cells
for (i = 0; i < (map->w*map->h); i++){
c = map->cell[i];
if (c && c->known && !haslos(player, c)) {
poss[nposs] = c;
nposs++;
}
}
if (amtleft > nposs) amtleft = nposs;
// forget cells...
for (i = 0; i < amtleft; i++) {
int n;
int sel;
sel = rnd(0,nposs-1);
poss[sel]->known = B_FALSE;
// shuffle down
for (n = i; n < (amtleft-1); n++) {
poss[n] = poss[n+1];
}
nposs--;
}
}
cell_t *getcellindir(cell_t *cell, int dir) {
cell_t *newcell;
int newx,newy;
int dt;
switch (dir) {
case D_N:
case D_S:
case D_E:
case D_W:
dt = DT_ORTH;
break;
case DC_N:
case DC_E:
case DC_S:
case DC_W:
case DC_NE:
case DC_SE:
case DC_SW:
case DC_NW:
dt = DT_COMPASS;
break;
default:
return NULL;
}
newx = cell->x + dirtox(dt, dir);
newy = cell->y + dirtoy(dt, dir);
newcell = getcellat(cell->map, newx,newy);
return newcell;
}
vault_t *getcellvault(cell_t *c) {
if (c->room) {
return c->room->vault;
}
return NULL;
}
cell_t *getclosestroomcell(lifeform_t *lf, int roomid) {
int i;
cell_t *c,*best = NULL;
int closest = 9999;
for (i = 0; i < lf->cell->map->w * lf->cell->map->h; i++) {
c = lf->cell->map->cell[i];
if ((getroomid(c) == roomid) & cellwalkable(lf, c, NULL)) {
if (getcelldist(lf->cell, c) < closest) {
best = c;
}
}
}
return best;
}
// select a new direction (random chance of turnung)
int getnewdigdir(cell_t *cell, int lastdir, int turnpct, int *moved) {
int foundvaliddir = B_FALSE;
int dir;
int tried[4], numtries;
int i;
int turned = B_FALSE;
cell_t *newcell;
int db = B_FALSE;
char err[BUFLEN];
// haven't tried any dirs yet
numtries = 0;
for (i = 0; i < MAXDIR_ORTH; i++) {
tried[i] = B_FALSE;
}
while (!foundvaliddir) { // keep going until we get a valid direction
if (numtries >= MAXDIR_ORTH) { // no valid dirs
return D_NONE; // (pick a new random spot and refresh tried dirs and current dir)
}
if (lastdir == D_UNKNOWN) {
// just pick a random dir
dir = rnd(0, MAXDIR_ORTH-1);
turned = B_TRUE;
} else {
// chance of changing dir
if (rnd(1,100) <= turnpct) {
dir = rnd(0, MAXDIR_ORTH-1);
turned = B_TRUE;
} else {
dir = lastdir;
}
}
// now validate the direction
if (db) printf("--- Trying %s...\n",getdirname(dir));
if (tried[dir] == B_TRUE) { // already found this dir to be invalid
lastdir = D_UNKNOWN;
if (db) printf("--- Already know %s is invalid.\n",getdirname(dir));
} else {
// check 1 cell ahead
newcell = getcellindir(cell, dir);
if (isnewcellok(newcell, err)) {
cell_t *newcell1, *newcell2;
// check 2 cells ahead and sidewars
newcell = getcellindir(newcell, dir);
if (newcell) {
switch (dir) {
case D_N:
case D_S:
newcell1 = getcellindir(newcell,D_E);
newcell2 = getcellindir(newcell,D_W);
break;
case D_E:
case D_W:
newcell1 = getcellindir(newcell,D_N);
newcell2 = getcellindir(newcell,D_S);
break;
default: // should never happen
newcell1 = NULL;
newcell2 = NULL;
break;
}
} else {
newcell1 = NULL;
newcell2 = NULL;
}
if (!isnewcellok(newcell, err)) {
if (db) printf("--- %s %s!\n",getdirname(dir), err);
tried[dir] = B_TRUE;
lastdir = D_UNKNOWN;
numtries++;
} else if (!isnewcellok(newcell1, err)) {
if (db) printf("--- %s %s!\n",getdirname(dir), err);
tried[dir] = B_TRUE;
lastdir = D_UNKNOWN;
numtries++;
} else if (!isnewcellok(newcell2, err)) {
if (db) printf("--- %s %s!\n",getdirname(dir), err);
tried[dir] = B_TRUE;
lastdir = D_UNKNOWN;
numtries++;
} else { // ok
if (db) printf("--- %s %s!\n",getdirname(dir), err);
foundvaliddir = B_TRUE;
}
} else {
if (db) printf("--- %s %s!\n",getdirname(dir), err);
tried[dir] = B_TRUE;
lastdir = D_UNKNOWN;
numtries++;
}
}
}
//newcell = getcellindir(cell, dir);
//printf("getrandomdigdir() - on cell %d,%d, returning dir %d (-> %d,%d)\n",
// cell->x, cell->y, dir, newcell->x, newcell->y);
if (turned) *moved = 0;
return dir;
}
char *getregionname(char *buf, map_t *m, int withlevel) {
region_t *r;
r = m->region;
if (withlevel) {
flag_t *f;
int x,y;
f = hasflag(m->flags, F_MAPCOORDS);
if (f) {
x = f->val[0];
y = f->val[1];
} else {
x = NA;
y = NA;
}
switch (r->rtype->id) {
case RG_WORLDMAP:
snprintf(buf, BUFLEN, "the surface(%d,%d)",x,y);
break;
case RG_FIRSTDUNGEON:
snprintf(buf, BUFLEN, "dungeon L%d", m->depth);
break;
case RG_HEAVEN:
snprintf(buf, BUFLEN, "the realm of gods");
break;
case RG_PIT:
snprintf(buf, BUFLEN, "a pit L%d", m->depth);
break;
default:
strcpy(buf, "?unknownregiontype??");
break;
}
} else {
switch (r->rtype->id) {
case RG_WORLDMAP:
strcpy(buf, "the surface");
break;
case RG_FIRSTDUNGEON:
strcpy(buf, "a dungeon");
break;
case RG_HEAVEN:
snprintf(buf, BUFLEN, "the realm of gods");
break;
case RG_PIT:
snprintf(buf, BUFLEN, "a pit");
break;
default:
strcpy(buf, "?unknownregiontype??");
break;
}
}
return buf;
}
cell_t *getrandomadjcell(cell_t *c, int wantempty, int allowexpand) {
return real_getrandomadjcell(c, wantempty, allowexpand, LOF_NEED, NULL);
}
cell_t *real_getrandomadjcell(cell_t *c, int wantempty, int allowexpand, enum LOFTYPE needlof, enum OBTYPE *dontwantob) {
int radius = 1;
int x,y;
cell_t *poss[MAXCANDIDATES];
int nposs = 0;
cell_t *new;
int done = B_FALSE;
while (!done) {
int numwithlof = 0;
for (y = c->y - radius ; y <= c->y + radius ; y++) {
for (x = c->x - radius ; x <= c->x + radius ; x++) {
new = getcellat(c->map, x, y);
if (new &&
(new != c) &&
(getcelldist(c,new) == radius) &&
haslof(c, new, needlof, NULL)) {
enum OBTYPE *badoid;
int ok = B_FALSE;
numwithlof++;
if (wantempty == WE_EMPTY) {
// make sure it's empty
if (isempty(new)) {
ok = B_TRUE;
}
} else if (wantempty == WE_WALKABLE) {
if (cellwalkable(NULL, new, NULL)) {
ok = B_TRUE;
}
} else if (wantempty == WE_PORTAL) {
if (cellwalkable(NULL, new, NULL) && !hasenterableobject(new) ) {
if (!hasobwithflag(new->obpile, F_DOOR)) {
ok = B_TRUE;
}
}
} else if (wantempty == WE_NOTWALL) {
if ((!new->type->solid) && !hasobwithflag(new->obpile, F_IMPASSABLE)) {
//if (!new->type->solid) {
ok = B_TRUE;
}
} else if (wantempty == WE_NOLF) {
if (!new->lf) ok = B_TRUE;
} else {
// always ok
ok = B_TRUE;
}
// obs we dont want?
if (dontwantob) {
for (badoid = dontwantob; (*badoid != OT_NONE) ; badoid++) {
if (hasob(new->obpile, *badoid)) {
ok = B_FALSE;
break;
}
}
}
if (ok) {
poss[nposs++] = new;
}
}
}
}
// found any possibilities ?
if (nposs) {
done = B_TRUE;
} else {
if (allowexpand) {
if (numwithlof) {
// increment radius
radius++;
} else {
return NULL;
}
} else {
return NULL;
}
}
}
return poss[rnd(0,nposs-1)];
}
cell_t *getrandomcell(map_t *map) {
int x,y;
cell_t *cell;
x = (rand() % map->w);
y = (rand() % map->h);
cell = getcellat(map, x, y);
assert(cell);
return cell;
}
cell_t *getrandomcelloftype(map_t *map, enum CELLTYPE id) {
cell_t *cell;
cell = getrandomcell(map);
while (cell->type->id != id) {
cell = getrandomcell(map);
}
return cell;
}
int getrandomdir(int dirtype) {
if (dirtype == DT_ORTH) {
return rnd(D_N, D_W);
} else { // ie. DT_COMPASS
return rnd(DC_N, DC_NW);
}
}
cell_t *getrandomroomcell(map_t *map, int roomid) {
int npossible = 0;
int selidx;
int x,y;
cell_t *c, **poss;
poss = malloc((map->w*map->h) * sizeof(cell_t));
npossible = 0;
for (y = 0; y < map->h; y++) {
for (x = 0; x < map->w; x++) {
c = getcellat(map, x, y);
// is this cell in the correct room and not a wall?
// using c->type->solid in case we have a room
// filled with boulders
//if (c && cellwalkable(NULL, c, NULL)) {
if (c && !c->type->solid) {
int ok = B_FALSE;
if (getroomid(c) == roomid) {
ok = B_TRUE;
} else if (roomid == ANYROOM) {
if (isroom(c) != -1) {
ok = B_TRUE;
}
}
if (ok) {
poss[npossible] = c;
npossible++;
}
}
}
}
if (npossible <= 0) {
return NULL;
}
selidx = rnd(0, npossible-1);
c = poss[selidx];
free(poss);
return c;
}
// popuplates retcell[] with all cells from room
void getroomcells(map_t *m, int roomid, cell_t **retcell, int *ncells) {
int i;
cell_t *c;
*ncells = 0;
for (i = 0; i < m->w*m->h; i++) {
c = m->cell[i];
if (c && (getroomid(c) == roomid)) {
retcell[*ncells] = c;
(*ncells)++;
}
}
}
// returns how slippery this cell is
// if slipob is set, return the most slippery object in it.
int getslipperyness(cell_t *c, object_t **slipob) {
object_t *o,*bestob = NULL;
int bestslip = 0;
int totalslip = 0;
if (slipob) *slipob = NULL;
for (o = c->obpile->first ; o ; o = o->next) {
int thisslip;
sumflags(o->flags, F_SLIPPERY, &thisslip, NULL, NULL);
if (thisslip > 0) {
if (thisslip > bestslip) {
bestob = o;
bestslip = thisslip;
}
}
thisslip *= o->amt;
totalslip += thisslip;
}
totalslip *= 2;
if (slipob) {
*slipob = bestob;
}
return totalslip;
}
cell_t *getstairdestination(object_t *o) {
flag_t *f;
cell_t *newcell = NULL;
f = hasflag(o->flags, F_MAPLINK);
if (f) {
map_t *newmap;
int newx,newy;
newmap = findmap(f->val[0]);
assert(newmap);
// prefer an object id if we have it
if (strlen(f->text)) {
object_t *o2;
long obid;
obid = atol(f->text);
o2 = findobidinmap(newmap, obid);
if (o2) {
newcell = getoblocation(o2);
} else {
dblog("stairs link to object %ld which no longer exists!",obid);
msg("stairs link to object %ld which no longer exists!",obid);
return NULL;
}
} else {
// otherwise look for x/y coors
newx = f->val[1];
newy = f->val[2];
// find dst x/y
newcell = getcellat(newmap, newx, newy);
}
}
return newcell;
}
object_t *hasenterableobject(cell_t *c) {
return hasobwithflag(c->obpile, F_CLIMBABLE);
}
object_t *hascloseddoor(cell_t *c) {
object_t *o;
int isopen;
for (o = c->obpile->first ; o ; o = o->next) {
if (isdoor(o, &isopen)) {
if (!isopen) {
return o;
}
}
}
return NULL;
}
lifeform_t *haslf(cell_t *c) {
if (c->lf && !isdead(c->lf)) {
return c->lf;
}
return NULL;
}
int hasobject(cell_t *c) {
if (c->obpile->first) {
return B_TRUE;
}
return B_FALSE;
}
int hasknownobject(cell_t *c) {
object_t *o;
for (o = c->obpile->first ; o ; o = o->next) {
if (o && canseeob(player, o)) {
return B_TRUE;
}
}
return B_FALSE;
}
object_t *hastrailof(obpile_t *op, lifeform_t *lf, enum OBTYPE oid, flag_t **tflag, lifeform_t *viewer) {
object_t *o;
flag_t *f;
for (o = op->first ; o ; o = o->next) {
if (viewer && !canseeob(viewer, o)) continue;
if ((oid == NA) || (o->type->id == oid)) {
f = hasflag(o->flags, F_TRAIL);
// raceid and lfid must match
if (f) {
if ((f->val[0] == lf->race->id) && (atoi(f->text) == lf->id)) {
if (tflag) {
*tflag = f;
}
return o;
}
}
}
}
return NULL;
}
void initmap(void) {
int vx[4],vy[4],i;
// habitats
// thingchance, obchance, vaultchance
addhabitat(H_DUNGEON, "dungeon", CT_CORRIDOR, CT_WALL, 3, 50, 30, 6);
addhabitat(H_FOREST, "forest", CT_GRASS, CT_WALL, 3, 75, 0, MAXVISRANGE);
addhabitat(H_HEAVEN, "heaven", CT_CORRIDOR, CT_WALL, 0, 0, 0, MAXVISRANGE);
addhabitat(H_PIT, "pit", CT_CORRIDOR, CT_WALL, 0, 0, 0, 5);
addhabitat(H_VILLAGE, "village", CT_GRASS, CT_WALL, 3, 70, 0, MAXVISRANGE);
// cell types
addcelltype(CT_WALL, "rock wall", '#', C_GREY, B_SOLID, B_OPAQUE, MT_STONE, 0);
addcelltype(CT_WALLWOOD, "wooden wall", '#', C_BROWN, B_SOLID, B_OPAQUE, MT_WOOD, 0);
addcelltype(CT_WALLGLASS, "glass wall", '#', C_CYAN, B_SOLID, B_TRANS, MT_GLASS, 0);
addcelltype(CT_WALLMETAL, "metal wall", '#', C_WHITE, B_SOLID, B_OPAQUE, MT_METAL, 0);
addcelltype(CT_ROOMWALL, "rock wall", '#', C_GREY, B_SOLID, B_OPAQUE, MT_STONE, 0);
addcelltype(CT_CORRIDOR, "rock floor", '.', C_GREY, B_EMPTY, B_TRANS, MT_STONE, 0);
addcelltype(CT_LOOPCORRIDOR, "rock floor", 'L', C_GREY, B_EMPTY, B_TRANS, MT_STONE, 0);
addcelltype(CT_FLOORWOOD, "wood floor", '.', C_BROWN, B_EMPTY, B_TRANS, MT_WOOD, 0);
addcelltype(CT_FLOORSHOP, "shop floor", '.', C_BROWN, B_EMPTY, B_TRANS, MT_WOOD, 0);
addcelltype(CT_ROOM, "rock floor", '.', C_GREY, B_EMPTY, B_TRANS, MT_STONE, 0);
addcelltype(CT_GRASS, "grass", '.', C_GREEN, B_EMPTY, B_TRANS, MT_PLANT, 0);
addcelltype(CT_DIRT, "dirt", '.', C_BROWN, B_EMPTY, B_TRANS, MT_STONE, 0);
addcelltype(CT_LOWFLOOR, "low rock floor", '.', C_GREY, B_EMPTY, B_TRANS, MT_STONE, -1);
// region types
addregiontype(RG_WORLDMAP, "World map", H_FOREST, 10, 0, D_NONE, B_TRUE);
addregiontype(RG_FIRSTDUNGEON, "First Dungeon", H_DUNGEON, 10, 3, D_DOWN, B_TRUE);
addregiontype(RG_HEAVEN, "Realm of Gods", H_HEAVEN, 1, 0, D_NONE, B_FALSE);
addregiontype(RG_PIT, "Pit", H_PIT, 1, 1, D_DOWN, B_FALSE);
// region definitions (outlines)
addregionoutline(RG_WORLDMAP);
// link to first dungeon
addregionthing(lastregionoutline, NA, 0, 0, RT_REGIONLINK, RG_FIRSTDUNGEON, "staircase going down");
// four villages
for (i = 0; i < 4; i++) {
vx[i] = 0; vy[i] = 0;
while ((vx[i] == 0) && (vy[i] == 0)) {
int n, distfromcentre;
distfromcentre = 2 + (i*2);
vx[i] = rnd(-distfromcentre,distfromcentre);
vy[i] = rnd(-distfromcentre,distfromcentre);
// check for other villages in this map...
for (n = 0; n < i; n++) {
if ((vx[n] == vx[i]) && (vy[n] == vy[i])) {
vx[i] = 0; vy[i] = 0; // invalidate!
break;
}
}
}
addregionthing(lastregionoutline, NA, vx[i], vy[i], RT_HABITAT, H_VILLAGE, NULL);
addregionthing(lastregionoutline, NA, vx[i], vy[i], RT_VAULT, NA, "food_shop");
addregionthing(lastregionoutline, NA, vx[i], vy[i], RT_VAULT, NA, "pub");
addregionthing(lastregionoutline, NA, vx[i], vy[i], RT_RNDVAULTWITHFLAG, F_VAULTISSHOP, NULL);
addregionthing(lastregionoutline, NA, vx[i], vy[i], RT_RNDVAULTWITHFLAG, F_VAULTISSHOP, NULL);
addregionthing(lastregionoutline, NA, vx[i], vy[i], RT_RNDVAULTWITHFLAG, F_VAULTISSHOP, NULL);
}
//vx = 0; vy = -1;
addregionoutline(RG_FIRSTDUNGEON);
addregionthing(lastregionoutline, 1, NA, NA, RT_RNDVAULTWITHFLAG, F_VAULTISPLAYERSTART, NULL);
addregionthing(lastregionoutline, 6, NA, NA, RT_VAULT, NA, "jimbos_lair");
addregionthing(lastregionoutline, 10, NA, NA, RT_RNDVAULTWITHFLAG, F_VAULTISSHRINE, NULL); // godstone on last floor
}
int isadjacent(cell_t *src, cell_t *dst) {
if (getcelldist(src, dst) == 1) {
return B_TRUE;
}
return B_FALSE;
}
int isdark(cell_t *c) {
switch (c->lit) {
case L_PERMDARK:
case L_NOTLIT:
return B_TRUE;
default:
break;
}
return B_FALSE;
}
int isdiggable(cell_t *c) {
switch (c->type->id) {
case CT_WALL: return B_TRUE;
case CT_ROOMWALL: return B_TRUE;
}
return B_FALSE;
}
// if isopen provided, returns whether or not the door is opened
int isdoor(object_t *o, int *isopen) {
int isdoor = B_FALSE;
if (hasflag(o->flags, F_DOOR)) {
isdoor = B_TRUE;
} else {
isdoor = B_FALSE;
}
if (isdoor) {
if (isopen) {
if (hasflag(o->flags, F_OPEN)) {
*isopen = B_TRUE;
} else {
*isopen = B_FALSE;
}
}
}
return isdoor;
}
int isempty(cell_t *c) {
if (!c) return B_FALSE;
if (c->type->solid) return B_FALSE;
if (c->lf) return B_FALSE;
if (!cellwalkable(NULL, c, NULL)) return B_FALSE;
return B_TRUE;
}
//returns TT_ based on what you can scan there
int isinscanrange(cell_t *c, void **thing, char *desc, glyph_t *glyph) {
flag_t *f;
flag_t *retflag[MAXCANDIDATES];
int nretflags;
int i;
// handle scanner
f = lfhasflag(player, F_DETECTLIFE);
if (f) {
if (c->lf) {
if (getcelldistorth(player->cell, c) <= f->val[0]) {
*thing = c->lf;
if (f->val[1] == B_TRUE) {
if (desc) {
real_getlfnamea(c->lf, desc, B_FALSE);
strcat(desc, " (detected)");
}
if (glyph) {
*glyph = *(getlfglyph(c->lf));
}
} else {
if (desc) {
char *p;
p = getsizetext(getlfsize(c->lf));
snprintf(desc, BUFLEN, "%s %s monster", isvowel(*p) ? "an" : "a", p);
}
if (glyph) {
// select glyph based on size
glyph->ch = '0' + ((int) getlfsize(c->lf));
glyph->colour = C_GREY;
}
}
return TT_MONSTER;
}
}
}
f = lfhasflag(player, F_DETECTMETAL);
if (f) {
if (getcelldistorth(player->cell, c) <= f->val[0]) {
if (c->lf && ismetal(getlfmaterial(c->lf)) ) {
*thing = c->lf;
if (glyph) {
glyph->ch = '*';
glyph->colour = C_GREY;
}
if (desc) snprintf(desc, BUFLEN, "something metallic");
return TT_MONSTER;
} else {
object_t *o;
for (o = c->obpile->first ; o ; o = o->next) {
if (ismetal(o->material->id)) {
*thing = o;
if (glyph) {
glyph->ch = '*';
glyph->colour = C_GREY;
}
if (desc) snprintf(desc, BUFLEN, "something metallic");
return TT_OBJECT;
}
}
}
}
}
// can hear them using master level listen skill?
if (c->lf && lfhasflagval(player, F_CANHEARLF, c->lf->id, NA, NA, NULL)) {
if (glyph) {
*glyph = *(getlfglyph(c->lf));
}
if (desc) {
real_getlfnamea(c->lf, desc, B_FALSE);
strcat(desc, " (heard)");
}
*thing = c->lf;
return TT_MONSTER;
}
// get list of all detected ob ids.
getflags(player->flags, retflag, &nretflags, F_DETECTOBS, F_NONE);
for (i = 0; i < nretflags; i++) {
f = retflag[i];
if (getcelldistorth(player->cell, c) <= f->val[0]) {
object_t *o;
for (o = c->obpile->first ; o ; o = o->next) {
if ((f->val[1] == NA) || (o->type->id == f->val[1])) {
if (!hasflag(o->flags, F_NOPICKUP) && !hasflag(o->flags, F_DOOR)) {
*thing = o;
if (glyph) {
glyph->ch = '*';
glyph->colour = C_BOLDGREEN;
}
if (desc) {
if (f->val[1] == NA) {
strcpy(desc, "a detected object");
} else {
getobname(o, desc, o->amt);
}
}
return TT_OBJECT;
}
}
}
}
}
return B_FALSE;
}
int islit(cell_t *c) {
switch (c->lit) {
case L_TEMP:
case L_PERMLIGHT:
return B_TRUE;
default:
break;
}
return B_FALSE;
}
int isloopdirok(cell_t *cell, int dir) {
int dirok = B_FALSE;
cell_t *newcell;
// is there a corridor in this direction?
newcell = getcellindir(cell, dir);
while (newcell) {
// got a corridor?
if (!newcell->type->solid) {
dirok = B_TRUE;
break;
}
// keep going
newcell = getcellindir(newcell, dir);
}
// we've either gone off the map or
// hit a corridor
return dirok;
}
int isnewcellok(cell_t *cell, char *err) {
if ( !cell) { // can't go that way
if (err) snprintf(err, BUFLEN,"goes off the map.");
return B_FALSE;
} else if ( !cell->type->solid) { // already an empty space there
if (err) snprintf(err, BUFLEN,"goes to an empty space (%d,%d)",cell->x,cell->y);
return B_FALSE;
}
// ok!
if (err) snprintf(err, BUFLEN, "OK!");
return B_TRUE;
}
int isnighttime(void) {
int hours,mins,secs;
splittime(&hours,&mins,&secs);
if ((hours < 5) || (hours >= 19)) {
return B_TRUE;
}
return B_FALSE;
}
int isonmap(map_t *map, int x, int y) {
if ((x < 0) || (y < 0)) {
return B_FALSE;
}
if ((x >= map->w) || (y >= map->h)) {
return B_FALSE;
}
return B_TRUE;
}
int isoutdoors(map_t *m) {
if (m->region && m->region->rtype->id == RG_WORLDMAP) {
return B_TRUE;
}
return B_FALSE;
}
int isroom(cell_t *c) {
if (c && (getroomid(c) >= 0)) {
return B_TRUE;
}
return B_FALSE;
}
int iswallindir(cell_t *cell, int dir) {
cell_t *newcell;
newcell = getcellindir(cell, dir);
if (!newcell) {
return B_TRUE;
}
if (newcell->type->solid) {
return B_TRUE;
}
return B_FALSE;
}
// search for unlinked pits/holes in roof in ADJACENT maps
// if we find any, add a matching end as close as we can in THIS map.
// returns then umber of holes linked.
int linkholes(map_t *map) {
map_t *adjmap;
cell_t *c;
object_t *o, *newob;
int x,y;
int dir;
int nlinked = 0;
for (dir = D_UP ; dir <= D_DOWN; dir++) {
adjmap = getmapindir(map, dir);
if (adjmap) {
for (y = 0; y < adjmap->h; y++) {
for (x = 0; x < adjmap->w; x++) {
c = getcellat(adjmap, x, y);
if (c) {
// does the adjacent map have an unlinked pit going to us?
for (o = c->obpile->first ; o ; o = o->next) {
if (hasflagval(o->flags, F_PIT, diropposite(dir), NA, NA, NULL) &&
!hasflag(o->flags, F_MAPLINK)) {
cell_t *c2;
objecttype_t *ot;
ot = getoppositestairs(o->type);
// make a link to it in this map, as close as possible to same pos
c2 = getcellat(map, x, y);
if (c2->lf || hasobid(c2->obpile, ot->id)) {
// this will automatically avoid lifeforms since we're using
// we_walkable rather than we_notwall. this saves problems
// with someine coming up underneath you!
c2 = real_getrandomadjcell(c2, WE_NOLF, B_ALLOWEXPAND, LOF_DONTNEED, &ot->id);
}
// clear out the cell if required
if (c2->type->solid) {
setcelltype(c2, c2->habitat->emptycelltype);
}
// note we specifically say DONT link the new hole, to avoid an infinite
// loop!
newob = addobject(c2->obpile, NULL, B_FALSE, B_FALSE, ot->id);
// link holes manually now.
linkstairs(newob, o);
// objects above fall down
if (dir == D_UP) {
obsfallthrough(c, o);
} else {
obsfallthrough(c2, newob);
}
nlinked++;
}
}
}
}
}
}
}
return nlinked;
}
// link the staircase 'o' to a free one in adjacent maps.
// o2 is options. if not probided, we will try to find
// something to link to ourself.
// returns TRUE if it failed because othermap doesn't exist.
int linkstairs(object_t *o, object_t *o2) {
map_t *othermap;
map_t *stairmap;
cell_t *staircell;
flag_t *f;
assert(o);
staircell = getoblocation(o);
stairmap = staircell->map;
if (o2) {
cell_t *othercell;
othercell = getoblocation(o2);
othermap = othercell->map;
} else {
objecttype_t *otherstairtype;
cell_t *c2;
int n, dir;
object_t *oo;
// find a valid other end
otherstairtype = getoppositestairs(o->type);
f = hasflag(o->flags, F_CLIMBABLE);
if (f) {
if (f->val[0] == D_UP) {
dir = -1;
} else {
dir = 1;
}
} else {
dblog("ERROR: stair object has no f_climbable!");
msg("ERROR: stair object has no f_climbable!");
assert(0 == 1);
}
// do stairs go to a particular region?
if (f->val[1] != NA) {
// if so, find the first map in that region (ie depth 1)
othermap = findregionmap(f->val[1], 1);
} else {
othermap = getmapindir(stairmap, f->val[0]);
}
if (othermap) {
int found = B_FALSE;
// find an empty staircase in other map
for (n = 0; n < othermap->w*othermap->h; n++) {
c2 = othermap->cell[n];
oo = hasob(c2->obpile, otherstairtype->id);
if (oo) {
// does it go nowhere?
if (!hasflag(oo->flags, F_MAPLINK)) {
o2 = oo;
found = B_TRUE;
break;
}
}
}
if (!found) {
dblog("ERROR - stairs link to existing map %d(depth %d), but it has no free stairs.",othermap->id, othermap->depth);
msg("ERROR - stairs link to existing map %d(depth %d), but it has no free stairs.",othermap->id, othermap->depth);
more();
}
} // end if othermap
} // end if !o2
if (o2) {
char obid[BUFLEN];
// link it to here!
snprintf(obid, BUFLEN, "%ld", o->id);
addflag(o2->flags, F_MAPLINK, stairmap->id, NA, NA, obid);
// link me to there
snprintf(obid, BUFLEN, "%ld", o2->id);
addflag(o->flags, F_MAPLINK, othermap->id, NA, NA, obid);
// now mark that we are not a new staircase to a new region anymore
f = hasflag(o->flags, F_CLIMBABLE);
if (f) {
f->val[1] = NA;
}
} else {
return B_TRUE;
}
return B_FALSE;
}
void makedoor(cell_t *cell, int openchance) {
object_t *o;
map_t *m;
char doorbuf[BUFLEN];
// can't have more than one door in a cell
if (hasobwithflag(cell->obpile, F_DOOR)) {
return;
}
m = cell->map;
setcelltype(cell, cell->habitat->emptycelltype);
if ((rnd(1,100) + m->depth) >= 66) {
strcpy(doorbuf, "iron door");
} else {
strcpy(doorbuf, "wooden door");
}
o = addob(cell->obpile, doorbuf);
if (o && (rnd(1,100) <= openchance)) {
opendoor(NULL, o);
} else {
int chance;
// door is closed - lock it?
// ie. at dungeon lev 10, chance is 2 in 6
// at dungeon lev 20, chance is 3 in 6
// ...
// at dungeon lev 50, chance is 5 in 6
chance = rolldie(1,6) - (m->depth / 10);
if (chance <= 1) {
addflag(o->flags, F_LOCKED, B_TRUE, getdoorlockdiff(m->depth), NA, NULL);
}
// make it secret?
chance = rolldie(1,6) - (m->depth / 10);
// difficulty:
// l1 = 20
// l10 = 25
// l20 = 30
if (chance <= 1) {
addflag(o->flags, F_SECRET, getdoorsecretdiff(m->depth), NA, NA, NULL);
}
}
}
void makelit(cell_t *c, enum LIGHTLEV how, int howlong) {
// don't override permenant light with temp light!
//if ((c->lit == L_PERMLIGHT) && (how == L_TEMP)) {
if (how == L_TEMP) {
//if ((c->lit == L_PERMLIGHT) || (c->lit == L_PERMDARK)) {
if (c->lit == L_PERMLIGHT) {
// light sources can't override permenant light
return;
}
if (c->lit == L_PERMDARK) {
// light sources can't override darkness spell
return;
}
}
if (howlong > 0) {
// TODO: use a stack here instead
c->origlit = c->lit;
c->origlittimer = c->littimer;
c->littimer = howlong;
}
c->lit = how;
}
void makelitradius(cell_t *c, int radius, enum LIGHTLEV how, int howlong) {
int x,y;
cell_t *c2;
int (*distfunc)(cell_t *, cell_t *);
if (radius <= 0) return;
if (radius == 1) {
distfunc = getcelldist;
} else {
distfunc = getcelldistorth;
}
for (y = c->y - radius ; y <= c->y + radius; y++) {
for (x = c->x - radius ; x <= c->x + radius; x++) {
c2 = getcellat(c->map, x, y);
if (c2 && (distfunc(c, c2) <= radius)) {
if (cellhaslos(c,c2)) {
makelit(c2, how,howlong);
}
}
}
}
}
void mapentereffects(map_t *m) {
int i;
cell_t *c;
flag_t *f;
for (i = 0; i < m->w * m->h; i++) {
vault_t *v;
// teleport shopkeepers back to their shops
c = m->cell[i];
if (c->lf && hasjob(c->lf, J_SHOPKEEPER) && !isplayer(c->lf)) {
f = lfhasflag(c->lf, F_OWNSSHOP);
if (f) {
cell_t *where;
int myshop;
myshop = f->val[0];
// find the closest cell of my shop
where = getclosestroomcell(c->lf, myshop);
if (where) {
movelf(c->lf, where);
}
}
}
v = getcellvault(c);
// replace people in the Inn
if (v && streq(v->id, "inn") && c->lf && (c->lf->race->id == R_HUMAN)) {
lifeform_t *lf;
killlf(c->lf);
lf = addmonster(c, R_HUMAN, NULL, B_TRUE, 1, B_FALSE, NULL);
addflag(lf->flags, F_STAYINROOM, getroomid(c), B_MAYCHASE, NA, NULL);
}
}
// monsters on the new level now get a bunch of turns to simulate them moving about when the player wasn't there.
if (m->lastplayervisit != -1) {
int nturns;
nturns = (curtime - m->lastplayervisit) / TICK_INTERVAL;
//nturns *= countlfs(m);
for (i = 0; i < nturns; i++) {
donextturn(m);
}
}
}
enum RACE parserace(char *name, flagpile_t *wantflags, enum JOB *wantjob) {
int donesomething;
char *p;
job_t *j;
// get params
donesomething = B_TRUE;
p = name;
while (donesomething) {
donesomething = B_FALSE;
if (strstarts(p, "sleeping ")) {
p += strlen("sleeping ");
if (wantflags) addflag(wantflags, F_ASLEEP, NA, NA, NA, NULL);
donesomething = B_TRUE;
}
}
// try removing suffixes for jobs
for (j = firstjob ; j ; j = j->next) {
char *ep;
char jobname[BUFLEN];
snprintf(jobname, BUFLEN, " %s", j->name);
jobname[1] = tolower(jobname[1]);
ep = strends(name, jobname);
if (ep) {
// got it
if (wantjob) *wantjob = j->id;
// now strip the suffix off, starting at the space before it
*ep = '\0';
break;
}
}
// now get raceid
if (streq(p, "random")) {
return R_RANDOM;
} else {
race_t *r;
r = findracebyname(p);
if (r) {
return r->id;
}
}
return R_NONE;
}
// returns # of dead ends removed.
int remove_deadends(map_t *m, int howmuch) {
enum CELLTYPE solidcell;
int i,n,count = 0;
solidcell = m->habitat->solidcelltype;
for (i = 0; i < howmuch; i++) {
for (n = 0; n < m->w * m->h; n++) {
cell_t *c;
c = m->cell[n];
if (countcellexits(c) == 1) {
// erase this cell
clearcell(c);
setcelltype(c, solidcell);
count++;
}
}
}
return count;
}
void setcellknown(cell_t *cell, int forcelev) {
enum SKILLLEVEL slev;
object_t *o;
if (forcelev > 0) {
slev = forcelev;
} else {
slev = getskill(player, SK_CARTOGRAPHY);
}
// photographic memory counts as novice level
if ((slev == PR_INEPT) && lfhasflag(player, F_PHOTOMEM)) {
slev = PR_NOVICE;
}
if (slev >= PR_NOVICE) {
cell->known = B_TRUE;
// default to remembering the cell's glyph
cell->knownglyph = cell->type->glyph;
// high cartography skill lets us remember certain objects...
if (slev >= PR_EXPERT) {
o = gettopobject(cell, B_TRUE);
if (o) {
cell->knownglyph = *(getglyph(o));
}
}
if (slev >= PR_ADEPT) {
for (o = cell->obpile->first ; o ; o = o->next) {
if ((o->type->obclass->id == OC_DFEATURE) || (o->type->obclass->id == OC_TERRAIN)) {
if (!issecretdoor(o)) {
cell->knownglyph = *(getglyph(o));
}
}
}
}
if (slev >= PR_BEGINNER) {
o = hasobwithflag(cell->obpile, F_CLIMBABLE);
if (o) {
cell->knownglyph = *(getglyph(o));
}
}
}
//getcellglyph(&(cell->knownglyph), cell, player);
}
void setcellknownradius(cell_t *centre, int forcelev, int radius, int dirtype) {
cell_t *cell[MAXCANDIDATES];
int ncells,i;
getradiuscells(centre, radius, dirtype, LOF_DONTNEED, B_TRUE, cell, &ncells, B_FALSE);
for (i = 0; i < ncells; i++) {
cell_t *c;
c = cell[i];
setcellknown(c, forcelev);
}
}
void setcelltype(cell_t *cell, enum CELLTYPE id) {
assert(cell);
cell->type = findcelltype(id);
assert(cell->type);
if (cell->type->solid) {
assert(!cell->obpile->first);
}
if ((gamemode == GM_GAMESTARTED) && haslos(player, cell)) {
needredraw = B_TRUE;
}
}
// returns true if something happened
int shattercell(cell_t *c, lifeform_t *fromlf, char *damstring) {
lifeform_t *target;
char targetname[BUFLEN], buf[BUFLEN];
object_t *o, *nexto;
int seen = B_FALSE;
int rv = B_FALSE;
target = c->lf;
if (target) {
getlfname(target, targetname);
}
// shatter solid glass cells
if (c->type->solid && willshatter(c->type->material->id)) {
rv = B_TRUE;
// announce
if (haslos(player, c)) {
msg("%s %s shatters!",needan(c->type->name) ? "An" : "A", c->type->name);
seen = B_TRUE;
} else {
// very loud
noise(c, NULL, NC_OTHER, 7, "shattering glass.", NULL);
}
if (target) {
if (seen) {
msg("%s %s showered in %s shards!", targetname, is(target), c->type->material->name);
}
losehp(target, rnd(1,100), DT_SLASH, fromlf, damstring); // BIG damage
}
// change cell type
setcelltype(c, c->habitat->emptycelltype);
// place shards
if (c->type->material->id == MT_GLASS) {
int numshards;
numshards = rnd(50,100);
snprintf(buf, BUFLEN, "%d pieces of broken glass",numshards);
addob(c->obpile, buf);
} else if (c->type->material->id == MT_ICE) {
int numshards;
numshards = rnd(50,100);
snprintf(buf, BUFLEN, "%d chunks of ice",numshards);
addob(c->obpile, buf);
}
}
// shatter lifeform-owned objects
if (target) {
for (o = target->pack->first ; o ; o = nexto) {
nexto = o->next;
if (willshatter(o->material->id)) {
if (shatter(o, B_TRUE, damstring, fromlf)) {
rv = B_TRUE;
}
}
}
}
// shatter cell objects
for (o = c->obpile->first ; o ; o = nexto) {
nexto = o->next;
if (willshatter(o->material->id)) {
if (shatter(o, B_TRUE, damstring, fromlf)) {
rv = B_TRUE;
}
}
}
return rv;
}
void updateknowncells(void) {
int x,y;
map_t *map;
object_t *wep;
int seenundead = B_FALSE;
map = player->cell->map;
wep = getweapon(player);
for (y = viewy; y < viewy + viewh; y++) {
for (x = viewx; x < viewx + vieww; x++) {
cell_t *cell;
cell = getcellat(map, x, y);
if (cell) {
//if ((player->cell == cell) || haslos(player, cell)) {
if (haslos(player, cell)) {
setcellknown(cell, B_FALSE);
if (cell->lf && lfhasflag(cell->lf, F_UNDEAD)) {
seenundead = B_TRUE;
}
}
}
}
}
}
int validateregions(void) {
regionoutline_t *ro;
int failed = B_FALSE;
// check outlines
for (ro = firstregionoutline ; ro ; ro = ro->next) {
int i;
for (i = 0 ; i < ro->nthings; i++) {
if (validateregionthing(&ro->thing[i])) {
failed = B_TRUE;
}
}
}
return failed;
}
int validateregionthing(regionthing_t *thing) {
switch (thing->whatkind) {
case RT_HABITAT:
if (!findhabitat(thing->value)) {
dblog("Invalid habitat %d specified in regionthing.", thing->value);
return B_TRUE;
}
break;
case RT_REGIONLINK:
if (!findregiontype(thing->value)) {
dblog("Invalid regionlink to regiontype %d specified in regionthing.", thing->value);
return B_TRUE;
}
break;
case RT_VAULT:
if (!findvault(thing->what)) {
dblog("Invalid rt_vault to vaulttype '%s' specified in regionthing.", thing->what);
return B_TRUE;
}
break;
case RT_RNDVAULTWITHFLAG:
if (!findvaultwithflag(thing->value)) {
dblog("Invalid rt_rndvaultwithflag specified in regionthing.");
return B_TRUE;
}
break;
}
return B_FALSE;
}
int wallstoleftright(cell_t *c, int dir) {
int d1,d2;
switch (dir) {
case D_N:
case D_S:
d1 = D_E; d2 = D_W; break;
case D_E:
case D_W:
d1 = D_N; d2 = D_S; break;
default:
return B_FALSE;
}
if (iswallindir(c,d1) && iswallindir(c,d2)) {
return B_TRUE;
}
return B_FALSE;
}