nexus/move.c

807 lines
17 KiB
C

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "attack.h"
#include "defs.h"
#include "flag.h"
#include "io.h"
#include "lf.h"
#include "map.h"
#include "move.h"
#include "nexus.h"
#include "objects.h"
#include "text.h"
extern lifeform_t *player;
extern int statdirty;
extern enum ERROR reason;
extern void *rdata;
int canandwillmove(lifeform_t *lf, int dir, enum ERROR *error) {
if (isplayer(lf)) {
if (canmove(lf, dir, error)) {
return B_TRUE;
}
} else {
if (canmove(lf, dir, error) && willmove(lf, dir, error)) {
return B_TRUE;
}
}
return B_FALSE;
}
int canmove(lifeform_t *lf, int dir, enum ERROR *error) {
cell_t *cell;
// default
if (error) {
*error = E_OK;
rdata = NULL;
}
if (lfhasflag(lf, F_GRAVBOOSTED)) {
if (error) *error = E_GRAVBOOSTED;
return B_FALSE;
}
// TODO: check if we are burdened, paralyzed, etc
cell = getcellindir(lf->cell, dir);
return cellwalkable(lf, cell, error);
}
int cellwalkable(lifeform_t *lf, cell_t *cell, enum ERROR *error) {
object_t *o;
// default
if (error) {
*error = E_OK;
rdata = NULL;
}
if (!cell || cell->type->solid) {
if (error) *error = E_WALLINWAY;
return B_FALSE;
}
for (o = cell->obpile->first ; o ; o = o->next) {
if (hasflag(o->flags, F_IMPASSABLE)) {
if (lf) {
if ((lf->race->material->id != MT_GAS) &&
(lf->race->material->id != MT_SLIME)) {
rdata = o;
if (error) *error = E_OBINWAY;
return B_FALSE;
}
} else {
rdata = o;
if (error) *error = E_OBINWAY;
return B_FALSE;
}
}
}
if (cell->lf && (cell->lf != lf)) {
if (error) *error = E_LFINWAY;
return B_FALSE;
}
return B_TRUE;
}
void dorandommove(lifeform_t *lf, int badmovesok) {
int dir;
int tries = 0;
int moveok;
enum ERROR why;
// find a valid direction
dir = getrandomdir(DT_COMPASS);
moveok = canandwillmove(lf, dir, &why);
if (!moveok && badmovesok) {
switch (why) {
// actually okay to move into someone
case E_WALLINWAY:
case E_LFINWAY:
moveok = B_TRUE;
break;
default:
break;
}
}
while (!moveok) {
// try next direction...
if (++dir > DC_NW) dir = DC_N;
if (++tries >= MAXDIR_COMPASS) {
rest(lf, B_TRUE);
return;
}
// check this direction...
moveok = canandwillmove(lf, dir, &why);
if (!moveok && badmovesok) {
switch (why) {
case E_WALLINWAY:
case E_LFINWAY:
moveok = B_TRUE;
break;
default:
break;
}
}
}
trymove(lf, dir);
}
int getdiraway(cell_t *src, cell_t *dst, int wantcheck) {
int d;
cell_t *c;
int maxdist=-1,bestdir=D_NONE;
for (d = DC_N; d <= DC_NW; d++) {
int thisdist = -1;
int ok = B_FALSE;
c = getcellindir(src, d);
if (!c) continue;
if (c == dst) {
// destination is the thing we're fleeing from!
thisdist = -1;
} else {
if (wantcheck) {
if (src->lf && canandwillmove(src->lf, d, NULL)) {
ok = B_TRUE;
} else if (!src->lf) {
ok = B_TRUE;
}
} else {
if (src->lf && cellwalkable(src->lf, c, NULL)) {
ok = B_TRUE;
} else if (!src->lf) {
ok = B_TRUE;
}
}
if (ok) {
thisdist = getcelldist(c, dst);
} else {
thisdist = -1;
}
if (thisdist > maxdist) {
maxdist = thisdist;
bestdir = d;
}
}
}
// TODO: handle ties
return bestdir;
}
int getdirtowards(cell_t *src, cell_t *dst, int wantcheck) {
int d;
cell_t *c;
int mindist=9999,bestdir=D_NONE;
for (d = DC_N; d <= DC_NW; d++) {
int ok = B_FALSE;
c = getcellindir(src, d);
if (!c) continue;
if (c == dst) {
// destination is adjacent!
bestdir = d;
break;
}
if (wantcheck) {
if (src->lf && canandwillmove(src->lf, d, NULL)) {
ok = B_TRUE;
} else if (!src->lf) {
ok = B_TRUE;
}
} else {
if (src->lf && cellwalkable(src->lf, c, NULL)) {
ok = B_TRUE;
} else if (!src->lf) {
ok = B_TRUE;
}
}
if (ok) {
int thisdist;
thisdist = getcelldist(c, dst);
if (thisdist < mindist) {
mindist = thisdist;
bestdir = d;
}
}
}
// TODO: handle ties
return bestdir;
}
int moveawayfrom(lifeform_t *lf, cell_t *dst) {
int dir;
int rv = B_TRUE;
// move towards them
dir = getdiraway(lf->cell, dst, B_TRUE);
if (dir != D_NONE) {
rv = trymove(lf, dir);
}
return rv;
}
// returns TRUE if something happened
int movelf(lifeform_t *lf, cell_t *newcell) {
object_t *o,*nexto;
char obname[BUFLEN],lfname[BUFLEN],buf[BUFLEN];
lifeform_t *l;
int didmsg = B_FALSE;
flag_t *f;
// update current cell
lf->cell->lf = NULL;
// if required, relink lifeform to new map
if (newcell->map != lf->cell->map) {
if (isplayer(lf)) {
statdirty = B_TRUE;
}
relinklf(lf, newcell->map);
}
// update lifeform
lf->cell = newcell;
// nothing should be in new cell..
assert(!newcell->lf);
// update new cell
newcell->lf = lf;
if (isbleeding(lf)) {
if (rnd(1,2) == 1) {
bleed(lf);
}
}
// check ground objects
if (!lfhasflag(lf, F_FLYING)) {
for (o = newcell->obpile->first ; o ; o = nexto ) {
nexto = o->next;
f = hasflag(o->flags, F_SHARP);
if (f) {
object_t *boots;
// has boots on?
boots = getequippedob(lf->pack, BP_FEET);
if (boots) {
// crunch the broken glass
getobname(o, obname, 1);
if (o->amt > 1) {
char *newname;
// we want 'xx steps on some pieces of broken glass'
// not 'xx steps on 5 pieces of broken glass'
newname = makeplural(obname);
newname = strrep(newname, "a ", "some ", NULL);
strcpy(obname, newname);
free(newname);
}
if (lf->controller == C_PLAYER) {
msg("You crush %s underfoot.",obname);
didmsg = B_TRUE;
} else if (haslos(player, newcell)) {
getlfname(lf, lfname);
capitalise(lfname);
msg("%s crushes %s.",lfname, obname);
didmsg = B_TRUE;
}
// kill object
removeob(o, o->amt);
} else {
// take damage
getobname(o, obname, 1);
if (lf->controller == C_PLAYER) {
msg("Ow - you step on %s!",obname);
didmsg = B_TRUE;
} else if (haslos(player, newcell)) {
getlfname(lf, lfname);
capitalise(lfname);
msg("%s steps on %s!",lfname, obname);
didmsg = B_TRUE;
}
sprintf(buf, "stepping on %s", obname);
losehp(lf, rnd(f->val[0],f->val[1]), DT_SLASH, NULL, buf);
}
}
} // end foreach object in cell
} // end if !flying
// update where player knows
// (but without a map you will then slowly forget it)
if (isplayer(lf)) {
updateknowncells();
}
// does anyone else see you?
for (l = newcell->map->lf ; l ; l = l->next) {
if (l != lf) {
if (haslos(l, newcell)) {
interrupt(lf);
}
}
}
return didmsg;
}
// basically this is a warpper for 'movelf' which
// does other game things like telling the player
// what is here.
int moveto(lifeform_t *lf, cell_t *newcell) {
char lfname[BUFLEN];
int didmsg;
getlfname(lf, lfname);
// actually do the move
didmsg = movelf(lf, newcell);
// tell player about things
if (!isdead(lf)) {
// some lifeforms can go through things
if (lf->race->material->id == MT_GAS) {
object_t *o;
char obname[BUFLEN];
for (o = newcell->obpile->first ; o ; o = o->next) {
if (hasflag(o->flags, F_IMPASSABLE)) {
getobname(o, obname, o->amt);
if (isplayer(lf)) {
msg("You seep around %s.", obname);
} else if (haslos(player, newcell)) {
msg("%s seeps around %s.", lfname, obname);
}
}
}
} else if (lf->race->material->id == MT_SLIME) {
object_t *o;
char obname[BUFLEN];
for (o = newcell->obpile->first ; o ; o = o->next) {
if (hasflag(o->flags, F_IMPASSABLE)) {
getobname(o, obname, o->amt);
if (isplayer(lf)) {
msg("You seep under %s.", obname);
} else if (haslos(player, newcell)) {
msg("%s seeps under %s.", lfname, obname);
}
}
}
}
// see objects on ground
if (isplayer(lf)) {
int numobs;
numobs = countobs(newcell->obpile);
if ((numobs == 0) && !newcell->writing) {
// just clear the message buffer
if (!didmsg) clearmsg();
} else { // tell player what is here
dolook(newcell);
}
}
}
return B_FALSE;
}
int movetowards(lifeform_t *lf, cell_t *dst) {
int dir;
int rv = B_TRUE;
// move towards them
dir = getdirtowards(lf->cell, dst, B_TRUE);
if (dir != D_NONE) {
rv = trymove(lf, dir);
}
return rv;
}
int opendoorat(lifeform_t *lf, cell_t *c) {
object_t *o;
int rv;
o = hasobwithflag(c->obpile, F_DOOR);
if (o) {
rv = opendoor(lf, o);
} else {
rv = B_TRUE;
}
return rv;
}
int opendoor(lifeform_t *lf, object_t *o) {
cell_t *doorcell;
char buf[BUFLEN];
char obname[BUFLEN];
flag_t *f;
doorcell = o->pile->where;
assert(doorcell);
getobname(o, obname, 1);
if (!isdoor(o, NULL)) {
return B_TRUE;
}
if (lf && lfhasflagval(lf, F_NOBODYPART, BP_HANDS, NA, NA, NULL)) {
if (isplayer(lf)) {
msg("You have no hands with which to open the door!");
}
return B_TRUE;
}
f = hasflag(o->flags, F_OPEN);
if (f) {
if (lf && isplayer(lf)) {
msg("It is already open!");
}
return B_TRUE;
} else {
// locked?
if (hasflag(o->flags, F_LOCKED)) {
if (lf && isplayer(lf)) {
msg("The door is locked.");
}
return B_TRUE;
} else {
// open it
addflag(o->flags, F_OPEN, B_TRUE, NA, NA, NULL);
f = hasflag(o->flags, F_IMPASSABLE);
if (f) killflag(f);
f = hasflag(o->flags, F_BLOCKSVIEW);
if (f) killflag(f);
if (lf) {
if (lf->controller == C_PLAYER) {
msg("You open %s.",obname);
} else {
if (haslos(player, lf->cell) && isadjacent(lf->cell, doorcell)) {
getlfname(lf, buf);
capitalise(buf);
msg("%s opens %s.",buf, obname);
} else if (haslos(player, doorcell)) {
capitalise(obname);
msg("%s opens.",obname);
}
}
taketime(lf, getmovespeed(lf));
touch(lf, o);
}
}
}
return B_FALSE;
}
int closedoorat(lifeform_t *lf, cell_t *c) {
object_t *o;
int rv;
o = hasobwithflag(c->obpile, F_DOOR);
if (o) {
rv = closedoor(lf, o);
} else {
rv = B_TRUE;
}
return rv;
}
int closedoor(lifeform_t *lf, object_t *o) {
cell_t *cell;
char buf[BUFLEN];
char obname[BUFLEN];
flag_t *f;
getobname(o, obname, 1);
if (!isdoor(o, NULL)) {
if (isplayer(lf)) {
msg("There is nothing to close there!");
}
return B_TRUE;
}
if (lf && lfhasflagval(lf, F_NOBODYPART, BP_HANDS, NA, NA, NULL)) {
if (isplayer(lf)) {
msg("You have no hands with which to close the door!");
}
return B_TRUE;
}
f = hasflag(o->flags, F_OPEN);
if (!f) {
if (lf && (lf->controller == C_PLAYER)) {
msg("It is already closed!");
}
return B_TRUE;
} else {
// close it
killflag(f);
addflag(o->flags, F_IMPASSABLE, B_TRUE, NA, NA, NULL);
addflag(o->flags, F_BLOCKSVIEW, B_TRUE, NA, NA, NULL);
if (lf) {
if (lf->controller == C_PLAYER) {
msg("You close %s.", obname);
} else {
if (haslos(player, lf->cell) && isadjacent(lf->cell, cell)) {
getlfname(lf, buf);
capitalise(buf);
msg("%s closes %s.",buf, obname);
} else if (haslos(player, cell)) {
capitalise(obname);
msg("%s closes.",obname);
}
}
taketime(lf, getmovespeed(lf));
touch(lf, o);
}
}
return B_FALSE;
}
int tryrun(lifeform_t *lf, int dir) {
if (!trymove(lf, dir)) {
addflag(lf->flags, F_RUNNING, dir, NA, NA, NULL);
}
return B_FALSE;
}
// try to pull lifeform towards cell c (or next to it)
int pullnextto(lifeform_t *lf, cell_t *c) {
int dir;
cell_t *dst = NULL;
dst = c;
while (dst->lf) {
dir = getdirtowards(dst, lf->cell, B_FALSE);
if (dir == D_NONE) {
return B_TRUE;
} else {
dst = getcellindir(dst, dir);
if (dst == lf->cell) {
return B_TRUE;
}
}
}
// is the path clear?
if (!dst || !haslof(lf, dst)) {
return B_TRUE;
}
if (isplayer(lf) || haslos(player, lf->cell)) {
char buf[BUFLEN];
getlfname(lf, buf);
msg("%s %s pulled %s!", buf, isplayer(lf) ? "are" : "is",
lfhasflag(lf, F_FLYING) ? "through the air" :
"along the ground");
}
movelf(lf, dst);
return B_FALSE;
}
// teleport somewhere, along with puffs of smoke etc
int teleportto(lifeform_t *lf, cell_t *c) {
char buf[BUFLEN];
if (!isplayer(lf) && haslos(player, lf->cell)) {
getlfname(lf, buf);
msg("%s disappears in a puff of smoke!", buf);
}
addob(lf->cell->obpile, "puff of smoke");
movelf(lf, c);
addob(lf->cell->obpile, "cloud of smoke");
if (isplayer(lf)) {
msg("Suddenly, your surroundings appear different!");
} else if (haslos(player, lf->cell)) {
getlfname(lf, buf);
msg("%s appears in a cloud of smoke!", buf);
}
// show any objects here, just like if we moved.
// BUT don't let dolook() clear the msg bar if there are
// no objects here.
if (isplayer(lf) && countobs(lf->cell->obpile)) {
dolook(lf->cell);
}
return B_FALSE;
}
int trymove(lifeform_t *lf, int dir) {
cell_t *cell;
enum ERROR errcode;
char buf[BUFLEN];
cell = getcellindir(lf->cell, dir);
if (canandwillmove(lf, dir, &errcode)) {
reason = E_OK;
moveto(lf, cell);
taketime(lf, getmovespeed(lf));
} else {
object_t *inway;
int door, dooropen;
reason = errcode;
switch (errcode) {
case E_WALLINWAY:
if (isplayer(lf)) {
msg("Ouch! You %s into a wall.", getmoveverb(lf));
} else if (haslos(player, lf->cell)) {
getlfname(lf, buf);
msg("%s %ss into a wall.", buf, getmoveverb(lf));
}
if (isblind(lf)) {
if (isplayer(lf)) {
// only take damage if we didn't know about this
if (!cell->known) {
sprintf(buf, "%sing into a wall", getmoveverb(lf));
losehp(lf, 1, DT_BASH, NULL, buf);
cell->known = B_TRUE;
taketime(lf, getmovespeed(lf));
}
} else {
sprintf(buf, "%sing into a wall", getmoveverb(lf));
losehp(lf, 1, DT_BASH, NULL, buf);
taketime(lf, getmovespeed(lf));
}
}
break;
case E_OBINWAY:
inway = (object_t *)rdata;
door = isdoor(inway, &dooropen);
if (door && !dooropen) {
if (isblind(lf)) {
// run into it
if (isplayer(lf)) {
if (cell->known) {
// try to open it
opendoor(lf, inway);
} else {
msg("Ouch! You %s into a door.", getmoveverb(lf));
cell->known = B_TRUE;
sprintf(buf, "%sing into a door", getmoveverb(lf));
losehp(lf, 1, DT_BASH, NULL, buf);
taketime(lf, getmovespeed(lf));
}
} else {
if (haslos(player, lf->cell)) {
getlfname(lf, buf);
msg("%s %ss into a wall.", buf, getmoveverb(lf));
}
sprintf(buf, "%sing into a door", getmoveverb(lf));
losehp(lf, 1, DT_BASH, NULL, buf);
taketime(lf, getmovespeed(lf));
}
} else {
// try to open it
opendoor(lf, inway);
}
} else {
// can we push this?
if (ispushable(inway)) {
if (canpush(lf, inway, dir)) {
// push it!
push(lf, inway, dir);
} else {
if (lf->controller == C_PLAYER) {
char obname[BUFLEN];
getobname(inway, obname, 1);
switch (reason) {
case E_TOOHEAVY:
msg("The %s is too heavy to move.",strchr(obname, ' ')+1);
break;
case E_INSUBSTANTIAL:
msg("You cannot push %s without a physical body!",strchr(obname, ' ')+1);
break;
default:
msg("The %s won't move for some reason.",strchr(obname, ' ')+1);
break;
}
}
}
} else { // object is in the way
if (lf->controller == C_PLAYER) {
char obname[BUFLEN];
getobname(inway, obname, 1);
msg("There is %s in your way.",obname);
}
}
}
break;
case E_LFINWAY:
// attack!
return attacklf(lf, cell->lf);
break;
case E_GRAVBOOSTED:
if (isplayer(lf)) {
msg("You try to move but are unable to lift your feet!");
taketime(lf, getmovespeed(lf));
}
break;
default:
break;
}
if (reason == E_OK) {
return B_FALSE;
} else {
return B_TRUE;
}
}
return B_FALSE;
}
int willmove(lifeform_t *lf, int dir, enum ERROR *error) {
cell_t *cell;
object_t *o;
enum IQBRACKET iq;
// default
if (error) {
*error = E_OK;
rdata = NULL;
}
cell = getcellindir(lf->cell, dir);
// check for cursed objects + animals
for (o = cell->obpile->first ; o ; o = o->next) {
if (!isplayer(lf)) {
if (o->blessed == B_CURSED) {
if (lfhasflag(lf, F_ANIMAL)) {
if (!lfhasflag(lf, F_FLYING)) {
return B_FALSE;
}
}
}
}
}
// check for dangerous objects
iq = getiqname(getattr(lf, A_IQ), NULL);
if (iq >= IQ_AVERAGE) {
flag_t *f;
object_t *o;
for (o = cell->obpile->first ; o ; o = o->next) {
// don't walk on sharp objects without boots
if (hasflag(o->flags, F_SHARP)) {
if (!getequippedob(lf->pack, BP_FEET)) {
return B_FALSE;
}
}
f = hasflag(o->flags, F_WALKDAM);
if (f) {
// are we immune to this?
if (!lfhasflagval(lf, F_DTIMMUNE, f->val[1], NA, NA, NULL)) {
return B_FALSE;
}
}
}
}
return B_TRUE;
}