nexus/ai.c

3847 lines
105 KiB
C

#include <assert.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ai.h"
#include "astar.h"
#include "attack.h"
#include "defs.h"
#include "flag.h"
#include "god.h"
#include "io.h"
#include "lf.h"
#include "map.h"
#include "move.h"
#include "nexus.h"
#include "objects.h"
#include "spell.h"
#include "text.h"
extern lifeform_t *player;
extern enum ERROR reason;
extern int playerhasmoved;
int wantdb = B_TRUE;
void addignorecell(lifeform_t *lf, cell_t *c) {
int howmany = 0;
if (!c || !lf) return;
// TEST: just have one ignorecell at once.
howmany = killflagsofid(lf->flags, F_IGNORECELL);
addtempflag(lf->flags, F_IGNORECELL, c->x, c->y, NA, NULL, 10);
//if (c && !lfhasflagval(lf, F_IGNORECELL, c->x, c->y, NA, NULL)) {
// addtempflag(lf->flags, F_IGNORECELL, c->x, c->y, NA, NULL, 10);
//}
}
// returns true on failure
int aiattack(lifeform_t *lf, lifeform_t *victim, int timelimit) {
int db = B_FALSE;
int innocentattack = 0;
flag_t *f;
if (lfhasflag(lf, F_DEBUG)) {
db = B_TRUE;
}
if (!canattack(lf)) {
if (db) dblog(".oO { canattack() is false }");
return B_TRUE;
}
// mindless?
/*
if (getattrbracket(getattr(lf, A_IQ), A_IQ, NULL) == IQ_MINDLESS) {
if (!isundead(lf) && (lf->race->raceclass->id != RC_PLANT)) {
dblog(".oO { i am mindless and not undead }");
return B_TRUE;
}
}
*/
// already targetting this lf?
f = lfhasflagval(lf, F_TARGETLF, victim->id, NA, NA, NULL);
if (f) {
if (db) dblog(".oO { i am already targetting this lf }");
if ((f->lifetime > 0) && (f->lifetime < timelimit)) {
f->lifetime = timelimit;
if (db) dblog(".oO { (flag lifetime updated) }");
}
return B_TRUE;
}
// feigning death?
if (lfhasflag(victim, F_FEIGNINGDEATH)) {
if (!lfhasflagval(lf, F_ATTACHEDTO, victim->id, NA, NA, NULL)) {
int checkpassed;
flag_t *alreadyfooled = NULL;
alreadyfooled = lfhasflagval(lf, F_FEIGNFOOLEDBY, victim->id, NA, NA, NULL);
if (alreadyfooled) {
checkpassed = B_FALSE;
} else {
int penalty;
penalty = (getcelldist(lf->cell, victim->cell)-1);
if (penalty < 0) penalty = 0;
penalty *= 15;
checkpassed = skillcheckvs(lf, SC_IQ, -penalty, victim, SC_WILL, 20+(gettr(victim)*2));
}
if (!checkpassed) {
if (db) dblog(".oO { attempted target fooled me with feign death. ignoring. }");
if (!alreadyfooled) {
addtempflag(lf->flags, F_FEIGNFOOLEDBY, victim->id, NA, NA, NULL, rnd(5,10));
}
return B_TRUE;
}
}
}
if (db) {
char lfname[BUFLEN],vicname[BUFLEN];
getlfname(lf, lfname);
getlfname(victim, vicname);
dblog(".oO { %s setting new target: %s }", lfname, vicname);
}
killflagsofid(lf->flags, F_AIPATH);
killflagsofid(lf->flags, F_TARGETLF);
killflagsofid(lf->flags, F_TARGETCELL);
if ((timelimit == PERMENANT) || (timelimit == UNLIMITED)) {
addflag(lf->flags, F_TARGETLF, victim->id, victim->cell->x, victim->cell->y, NULL);
} else {
addtempflag(lf->flags, F_TARGETLF, victim->id , victim->cell->x , victim->cell->y, NULL,timelimit);
}
// tell the player
if (areallies(player, lf) && cantalk(lf)) {
char text[BUFLEN];
if (cansee(lf, victim)) {
real_getlfname(victim, text, NULL, B_NOSHOWALL, B_CURRACE);
} else {
strcpy(text, "something");
}
if (cansee(player, victim)) {
sayphrase(lf, SP_ALLY_ATTACK, SV_SHOUT, NA, text, victim);
} else {
sayphrase(lf, SP_ALLY_ATTACKUNSEEN, SV_SHOUT, NA, text, victim);
}
} else {
if (!lfhasflag(lf, F_HIDING)) makenoise(lf, N_GETANGRY);
}
// become hostile?
if (isplayer(victim) && !hasflag(lf->flags, F_HOSTILE) ) {
addflag(lf->flags, F_HOSTILE, B_TRUE, NA, NA, NULL);
//innocentattack = 1;
}
// change allegience ?
if (!areenemies(lf, victim)) {
if (getallegiance(victim) == AL_FRIENDLY) {
killflagsofid(lf->flags, F_FRIENDLY);
innocentattack = 3;
}
}
// no longer a pet
f = lfhasflagval(lf, F_PETOF, victim->id, NA, NA, NULL);
if (f) {
if (isplayer(victim)) innocentattack = 3;
killflag(f);
}
return B_FALSE;
}
void ailoscheck(lifeform_t *lf) {
int i,ok = B_TRUE;
cell_t *c;
for (i = 0; i < lf->nlos; i++) {
c = lf->los[i];
if (c->known != B_TRUE) {
if ((c->known < PR_INEPT) || (c->known > PR_MASTER)) {
ok = B_FALSE; break;
}
}
if ((c->visited != B_FALSE) && (c->visited != B_TRUE)) {
ok = B_FALSE; break;
}
if (c->lf && (c->lf->id == 65432)) { // ie. will crash if c->lf is invalid
ok = B_FALSE; break;
}
}
if (!ok) {
msg("FATAL: corrupt los[] array for %s",lf->race->id);
assert("Corrupt los array" == 0);
}
}
// A* algorithm. Returns F_AIPATH flag.
flag_t *ai_createpathto(lifeform_t *lf, cell_t *targcell) {
char *pathbuf;
cell_t *pathcell[MAX_PATHFIND_STEPS];
int pathlen = 0;
int done = B_FALSE,i,n, first = B_TRUE;
node_t open[MAX_PATHFIND_ADJ];
node_t closed[MAX_PATHFIND_ADJ];
node_t *cur;
int nopen = 0,nclosed = 0;
int db = B_FALSE;
char lfname[BUFLEN];
flag_t *newf;
checkflagpile(lf->flags); // debug
if (!db && lfhasflag(lf, F_DEBUG)) db = B_TRUE;
real_getlfname(lf, lfname, NULL, B_SHOWALL, B_CURRACE);
if (lf->cell->map != targcell->map) {
if (db) dblog("%s: pathfind destination on different level", lfname);
return NULL;
}
if (db) dblog("%s pathfind - finding path from %d,%d to %d,%d\n",lfname, lf->cell->x,lf->cell->y,
targcell->x,targcell->y);
// add starting cell to open list
open[0].c = lf->cell;
open[0].fromstart = 0;
open[0].heur = calcheuristic(lf->cell, targcell);
open[0].cost = open[0].fromstart + open[0].cost;
open[0].parent = NULL;
nopen = 1;
// NEED: calccosts(node, parentcell, endcell);
while (!done) {
// if open list empty?
if (!nopen) {
// if so, there is NO path. fail.
if (db) dblog("%s pathfind - open list is empty. FAILED.",lfname);
return NULL;
}
// find lowest COST in open list. this is cur.
// move node[cur] to closed list
closed[nclosed] = open[0];
cur = &closed[nclosed];
nclosed++;
for (i = 0; i < nopen-1; i++) {
open[i] = open[i+1];
}
nopen--;
//if (db) dblog("%s pathfind - lowest cost node on openlist is %d,%d",lfname,cur->c->x, cur->c->y);
// is node[cur] the target cell?
if (cur->c == targcell) {
// if so, we've found a path. now need to populate
//if (db) dblog("%s pathfind - at target cell - success!",lfname);
done = B_TRUE;
} else {
// for "adjcell" in 8 squares around it:
for (i = DC_N; i <= DC_NW; i++) {
cell_t *adjcell;
int walkable,found = B_FALSE, ok = B_FALSE;
enum ERROR whynot;
adjcell = getcellindir(cur->c, i);
if (!adjcell) continue;
//if (db) dblog("%s pathfind - checking %s",lfname, getdirnameshort(i));
// already on closed list?
for (n = 0; n < nclosed; n++) {
if (closed[n].c == adjcell) {
found = B_TRUE;
break;
}
}
if (found) {
// already on closed list. ignore.
//if (db) dblog(" %s (%d,%d): already on closed list. ignore.",getdirnameshort(i),
// adjcell->x, adjcell->y);
continue;
}
walkable = cellwalkable(lf, adjcell, &whynot);
ok = B_FALSE;
if (lfhasflagval(lf, F_IGNORECELL, adjcell->x, adjcell->y, NA, NULL)) {
ok = B_FALSE;
} else if (walkable) {
ok = B_TRUE;
} else if (celldangerous(lf, adjcell, B_TRUE, NULL) && haslos(lf, adjcell)) {
// ignore dangerous cells only if we can see them.
ok = B_FALSE;
} else {
if (whynot == E_LFINWAY) {
if (isadjacent(adjcell, lf->cell)) {
ok = B_FALSE;
} else {
ok = B_TRUE;
}
} else if (whynot == E_DOORINWAY) {
if (canopendoors(lf)) {
ok = B_TRUE;
} else {
ok = B_FALSE;
}
} else {
ok = B_FALSE;
}
}
if (ok) {
int openpos = -1;
// adjcell is walkable.
for (n = 0; n < nopen; n++) {
if (open[n].c == adjcell) {
openpos = n;
break;
}
}
if (openpos != -1) {
int newfromstart;
node_t temp;
// adjcell is already on open list
//if (db) dblog(" %s (%d,%d): on open list (position %d).",getdirnameshort(i),
// adjcell->x, adjcell->y, openpos);
// recalc fromstart of adjcell using node[cur] as a parent.
newfromstart = calcg(lf, open[openpos].c, cur, i);
if (newfromstart < open[openpos].fromstart) {
// path through node[cur] is better.
//if (db) dblog(" %s (%d,%d): better cost - recalcing.",
// getdirnameshort(i),
// adjcell->x, adjcell->y);
// change parent of adjcell to node[cur]
// and recalc new costings (and re-sort list)
temp = open[openpos];
temp.parent = cur;
assert(isadjacent(temp.c, temp.parent->c));
temp.fromstart = calcg(lf, temp.c, temp.parent, i);
temp.heur = calcheuristic(temp.c, targcell);
temp.cost = temp.fromstart + temp.heur;
// remove adjcell from open list.
for (n = openpos; n < nopen; n++) {
open[n] = open[n+1];
}
nopen--;
// re-add adjcell at correct pos;
insert(&temp, open, &nopen);
}
} else {
// not on open list
node_t temp;
//if (db) dblog(" %s (%d,%d): not on openlist. inserting.",
// getdirnameshort(i),
// adjcell->x, adjcell->y);
// calc costs
temp.c = adjcell;
temp.parent = cur;
assert(isadjacent(temp.c, temp.parent->c));
temp.fromstart = calcg(lf, temp.c, temp.parent, i);
temp.heur = calcheuristic(adjcell, targcell);
temp.cost = temp.fromstart + temp.heur;
insert(&temp, open, &nopen);
}
} else {
// !walkable - ignore it.
//if (db) dblog(" %s (%d,%d): not walkable - ignoring.",
// getdirnameshort(i),
// adjcell->x, adjcell->y);
}
}
}
}
checkflagpile(lf->flags); // debug
// work backwards from node[cur] (ie. targcell) following parents.
// populate path and return
if (db) dblog("%s - found path!\n",lfname);
while (cur) {
pathcell[pathlen++] = cur->c;
if (pathlen >= MAX_PATHFIND_STEPS) {
if (db) dblog("%s pathfind - path too long (> %d). FAILED.",MAX_PATHFIND_STEPS);
return NULL;
}
cur = cur->parent;
}
checkflagpile(lf->flags); // debug
pathbuf = malloc(pathlen*6*sizeof(char));
strcpy(pathbuf, "");
checkflagpile(lf->flags); // debug
for (i = pathlen-1; i >= 0 ; i--) {
char smallbuf[BUFLENTINY];
// don't include starting cell
if (pathcell[i] == lf->cell) continue;
sprintf(smallbuf, "%s%d,%d",first ? "" : "-", pathcell[i]->x, pathcell[i]->y);
first = B_FALSE;
strcat(pathbuf, smallbuf);
}
if (db) dblog("* %s - path takes %d steps. ", lfname, pathlen);
if (db) dblog("* %s - pathbuf: [%s] ", lfname, pathbuf);
checkflagpile(lf->flags); // debug
newf = addflag(lf->flags, F_AIPATH, targcell->x, targcell->y, NA, pathbuf);
free(pathbuf);
checkflagpile(lf->flags); // debug
return newf;
}
// f is a flag of type F_AIPATH
//
// returns the next cell in the path, and removes next cell
// from the list.
//
// if there are no more cells in the path, remove the path.
cell_t *ai_getnextcellinpath(lifeform_t *lf) {
cell_t *c;
flag_t *f;
char *loctext;
char *p,buf[BUFLEN];
int x,y;
f = lfhasflag(lf, F_AIPATH);
if (!f) return NULL;
loctext = strdup(f->text);
p = loctext;
p = readuntil(buf, loctext,',');
x = atoi(buf);
p = readuntil(buf, p,'-');
y = atoi(buf);
free(loctext);
c = getcellat(lf->cell->map, x,y);
return c;
}
// returns TRUE on error
int ai_popnextcellinpath(lifeform_t *lf) {
flag_t *f;
char *loctext;
char *p,buf[BUFLEN];
f = lfhasflag(lf, F_AIPATH);
if (!f) return B_TRUE;
loctext = strdup(f->text);
p = loctext;
p = readuntil(buf, loctext,',');
p = readuntil(buf, p,'-');
if (strlen(p)) {
free(f->text);
f->text = strdup(p);
} else {
killflag(f);
}
free(loctext);
return B_FALSE;
}
enum OBTYPE aigetattackspell(lifeform_t *lf, lifeform_t *victim) {
flag_t *retflag[MAXCANDIDATES];
int nretflags = 0;
flag_t *f;
enum OBTYPE poss[MAXPILEOBS];
int nposs = 0,i;
int db = B_FALSE;
if (lfhasflag(lf, F_DEBUG)) {
db = B_TRUE;
}
if (db) {
dblog(".oO { looking for attack spells/abils }");
}
if (lfhasflag(lf, F_PHANTASM)) {
if (db) dblog(".oO { no attack spell because i am a phantasm }");
return OT_NONE;
}
if (missingspellcastob(lf)) {
if (db) dblog(".oO { Cannot cast spell, I don't have my spellcast object }");
return OT_NONE;
}
getflags(lf->flags, retflag, &nretflags, F_CANCAST, F_CANWILL, F_NONE);
for (i = 0; i < nretflags; i++) {
f = retflag[i];
if (aispellok(lf, f->val[0], victim, F_AICASTTOATTACK)) {
enum CASTTYPE ctype;
int ok = B_TRUE;
ctype = getcasttype(lf, f->val[0]);
switch (ctype) {
case CT_GAZE: // gaze into victim's eyes: they must be able to see you and vice versa
if (!cansee(victim, lf) || !cansee(lf, victim)) {
if (db) dblog(".oO { Cannot cast spell, no 2way los for gaze attack or victims eyes covered }");
ok = B_FALSE;
}
break;
case CT_EYESPIT: // spit into the victim's eyes. lof is checked in aispellok
if (!cansee(victim, lf) ) {
if (db) dblog(".oO { Cannot cast spell, no 2way los for gaze attack or victims eyes covered }");
ok = B_FALSE;
}
break;
default:
break;
}
if (ok) {
// lycanthropes always change to animal form at midnight.
if (gettimephase() == TP_MIDNIGHT) {
flag_t *lyf;
lyf = lfhasflag(lf, F_LYCANTHROPE);
if ((f->val[0] == OT_S_SHAPESHIFT) &&
lyf && (lyf->val[0] != 0)) {
if (ispolymorphed(lf)) {
// never change to human form.
ok = B_FALSE;
} else {
// always change to animal form.
// note: this should have already happened though,
// during donextturn();
return f->val[0];
}
}
}
// still ok?
if (ok) {
poss[nposs] = f->val[0];
nposs++;
}
}
}
}
// select a random one
if (nposs > 0) {
int sel;
if (db) {
char lfname[BUFLEN];
getlfname(lf,lfname);
dblog(".oO { %s i have %d valid spells/abils. using one. }", lfname, nposs);
}
sel = rnd(0,nposs-1);
return poss[sel];
}
return OT_NONE;
}
enum OBTYPE aigetfleespell(lifeform_t *lf) {
flag_t *f;
enum OBTYPE poss[MAXPILEOBS];
int nposs = 0,i;
int db = B_FALSE;
lifeform_t *fleefrom = NULL;
flag_t *retflag[MAXCANDIDATES];
int nretflags = 0;
if (lfhasflag(lf, F_DEBUG)) {
db = B_TRUE;
}
if (lfhasflag(lf, F_PHANTASM)) {
if (db) dblog(".oO { no flee spell because i am a phantasm }");
return OT_NONE;
}
f = lfhasflag(lf, F_FLEEFROM);
if (f) {
fleefrom = findlf(lf->cell->map, f->val[0]);
}
getflags(lf->flags, retflag, &nretflags, F_CANCAST, F_CANWILL, F_NONE);
for (i = 0; i < nretflags; i++) {
f = retflag[i];
if (aispellok(lf, f->val[0], fleefrom, F_AICASTTOFLEE)) {
poss[nposs] = f->val[0];
nposs++;
}
}
// select a random one
if (nposs > 0) {
int sel;
sel = rnd(0,nposs-1);
return poss[sel];
}
return OT_NONE;
}
// how long with this monster chase you for?
int aigetchasetime(lifeform_t *lf) {
flag_t *f;
f = lfhasflag(lf, F_FOLLOWTIME);
if (f) return f->val[0];
return DEF_AIFOLLOWTIME;
}
// this function assumes that you can't just SEE the target!
cell_t *aigetlastknownpos(lifeform_t *lf, lifeform_t *target, int *lastx, int *lasty, int *lastdir) {
flag_t *f, *tflag, *bestflag = NULL;
cell_t *c = NULL,*trailcell = NULL,*finalcell = NULL;
int besttime = -1;
int locallastdir = D_NONE;
int i;
// defaults
if (lastx) *lastx = NA;
if (lasty) *lasty = NA;
if (lastdir) *lastdir = D_NONE;
// if within scent range... we 'magically' know their location.
if (getcelldist(lf->cell, target->cell) <= getsmellrange(lf)) {
if (lastx) *lastx = target->cell->x;
if (lasty) *lasty = target->cell->y;
if (lastdir) *lastdir = D_NONE;
return target->cell;
}
// do we remember the player's last known location ?
f = ispetortarget(lf, target);
if (f) {
if (f->id == F_PETOF) {
// cheat.
finalcell = target->cell;
if (lastx) *lastx = finalcell->x;
if (lasty) *lasty = finalcell->y;
if (lastdir) *lastdir = D_NONE;
return finalcell;
} else {
c = getcellat(lf->cell->map, f->val[1], f->val[2]);
}
//if (c && cellwalkable(lf, c, NULL)) {
if (c) {
if (lastx) *lastx = c->x;
if (lasty) *lasty = c->y;
if (strlen(f->text)) {
locallastdir = atoi(f->text);
}
trailcell = c;
if (lastx) *lastx = c->x;
if (lasty) *lasty = c->y;
}
}
// if not, check for the most recent scent/footprints first
if (!trailcell) {
c = NULL;
lf->loslock = B_TRUE;
for (i = 0; i < lf->nlos; i++) {
if (hastrailof(lf->los[i]->obpile, target, NA, &tflag, lf)) {
if (tflag->lifetime > besttime) {
besttime = tflag->lifetime;
bestflag = tflag;
c = lf->los[i];
}
}
}
lf->loslock = B_FALSE;
if (bestflag && c) {
if (lastx) *lastx = c->x;
if (lasty) *lasty = c->y;
// can only obtain direction from footprints if your
// tracking skill is high enough.
if (bestflag->val[2] == S_SIGHT) {
if (getskill(lf, SK_PERCEPTION) >= PR_SKILLED) {
locallastdir = bestflag->val[1];
} else {
locallastdir = D_NONE;
}
} else {
locallastdir = bestflag->val[1];
}
trailcell = c;
}
}
// if we found somewhere, follow any trails out of there
if (trailcell) {
finalcell = trailcell;
if (locallastdir != D_NONE) {
cell_t *c;
// follow the trail
c = getcellindir(trailcell, locallastdir);
if (getcelldist(c, target->cell) < getcelldist(trailcell, target->cell)) {
locallastdir = D_NONE;
finalcell = c;
}
}
if (lastx) *lastx = finalcell->x;
if (lasty) *lasty = finalcell->y;
}
// return last movement direction
if (lastdir) {
*lastdir = locallastdir;
}
// just in case...
if (!finalcell) {
if ((*lastx != NA) && (*lasty != NA)) {
finalcell = getcellat(lf->cell->map, *lastx, *lasty);
}
}
return finalcell;
}
object_t *aigetrangedattack(lifeform_t *lf, lifeform_t *target, enum RANGEATTACK *ra, int *range) {
int db = B_FALSE;
enum ATTRBRACKET iqb;
object_t *o;
if (lfhasflag(lf, F_DEBUG)) db = B_TRUE;
if (lfhasflag(lf, F_STUNNED)) {
if (db) dblog(".oO { no ranged attack because i am stunned }");
if (ra) *ra = RA_NONE;
return NULL;
}
if (lfhasflag(lf, F_RAGE)) {
if (db) dblog(".oO { no ranged attack because i am enraged }");
if (ra) *ra = RA_NONE;
return NULL;
}
if (lfhasflag(lf, F_FEIGNINGDEATH) && target && (getcelldist(lf->cell, target->cell) == 1)) {
if (db) dblog(".oO { no ranged attack because i am feigning death and adj to my target }");
if (ra) *ra = RA_NONE;
return NULL;
}
iqb = getattrbracket(getattr(lf, A_IQ), A_IQ, NULL);
if (iqb <= IQ_ANIMAL) {
// animal and lower intelligence won't use ranged
// attacks.
if (!lfhasflag(lf, F_WILLTHROW)) {
if (db) dblog(".oO { no ranged attack due to low iq. }");
if (ra) *ra = RA_NONE;
return NULL;
}
}
o = getfirearm(lf);
if (o && getammo(o) && canshoot(lf, NULL)) {
if (db) {
char gunname[BUFLEN];
getobname(o, gunname, o->amt);
if (db) dblog(".oO { will fire my gun (%s) at target. }",gunname);
}
if (ra) *ra = RA_GUN;
if (range) *range = getfirearmrange(o);
return o;
}
// do we have a wand we can zap?
if (lfhasflag(lf, F_HUMANOID) || hasbp(lf, BP_HANDS)) {
if (lfhasflag(lf, F_FLEEFROM)) {
o = aigetwand(lf, F_AICASTTOFLEE);
} else {
o = aigetwand(lf, F_AICASTTOATTACK);
}
if (o && canoperate(lf, o, NULL)) {
if (db) dblog(".oO { will zap %s instead of moving }", o->type->name);
if (ra) *ra = RA_WAND;
if (range) *range = getvisrange(lf, B_TRUE); // ie unlimited
return o;
}
}
// can we attack by throwing something?
if (hasbp(lf, BP_HANDS) && !lfhasflag(lf, F_STUNNED)) {
o = getbestthrowmissile(lf, target);
if (o) {
if (db) dblog(".oO { will throw %s at my target }", o->type->name);
if (ra) *ra = RA_THROW;
if (range) {
if (hasflag(o->flags, F_POWDER)) {
*range = 1;
} else {
*range = getmaxthrowrange(lf, o);
}
}
return o;
}
}
if (db) dblog(".oO { found no ranged attack. }");
if (ra) *ra = RA_NONE;
if (range) *range = 0;
return NULL;
}
int aigetspelltarget(lifeform_t *lf, objecttype_t *spelltype, lifeform_t *victim, lifeform_t **spelllf, cell_t **spellcell, object_t **spellob, enum FLAG purpose) {
int specialcase = B_FALSE;
flag_t *f;
enum SPELLTARGET spelltarg = ST_NONE;
// default - at victim.
if (spelllf) *spelllf = victim;
if (spellcell) *spellcell = victim->cell;
if (spellob) *spellob = NULL;
f = lfhasflagval(lf, F_AISPELLTARGETOVERRIDE, spelltype->id, NA, NA, NULL);
if (f) {
spelltarg = f->val[1];
} else {
f = hasflag(spelltype->flags, purpose);
if (f) {
spelltarg = f->val[0];
}
}
switch (spelltarg) {
case ST_VICTIM:
// at victim.
if (spelllf) *spelllf = victim;
if (spellcell) *spellcell = victim->cell;
if (spellob) *spellob = NULL;
break;
case ST_ADJSELF: // cast at myself when next to victim
if (getcelldist(lf->cell, victim->cell) <= 1) {
if (spelllf) *spelllf = lf;
if (spellcell) *spellcell = lf->cell;
if (spellob) *spellob = NULL;
}
break;
case ST_ADJVICTIM: // cast at victim when next to victim
if (getcelldist(lf->cell, victim->cell) <= 1) {
if (spelllf) *spelllf = victim;
if (spellcell) *spellcell = victim->cell;
if (spellob) *spellob = NULL;
}
break;
case ST_SELF:
if (spelllf) *spelllf = lf;
if (spellcell) *spellcell = lf->cell;
if (spellob) *spellob = NULL;
break;
case ST_ANYWHERE:
if (spelllf) *spelllf = NULL;
if (spellcell) *spellcell = NULL;
if (spellob) *spellob = NULL;
break;
case ST_SPECIAL:
specialcase = B_TRUE;
break;
case ST_NONE:
break;
}
if (specialcase) {
if (spelltype->id == OT_A_CLIMB) {
int i,nposs = 0;
cell_t *poss[MAXCANDIDATES];
// cell in sight and adjacent? aispellok() should have already confirmed
// that there will be at least one of these.
for (i = 1; i < lf->nlos; i++) {
// using getcelldirorth to make sure direction is orthogonal
if (!lf->los[i]->lf && lf->los[i]->type->solid && (getcelldistorth(lf->cell, lf->los[i]) == 1)) {
poss[nposs++] = lf->los[i];
}
}
if (!nposs) {
if (spellcell) spellcell = NULL;
if (spelllf) *spelllf = NULL;
if (spellob) *spellob = NULL;
return B_TRUE;
}
if (spellcell) {
*spellcell = poss[rnd(0,nposs-1)];
}
if (spelllf) *spelllf = NULL;
if (spellob) *spellob = NULL;
} else if ((spelltype->id == OT_S_ANIMATESTATUE) || (spelltype->id == OT_S_ANIMATETREE)) {
cell_t *cell[MAXCANDIDATES],*poss[MAXCANDIDATES];
int ncells,i,nposs = 0;
enum OBTYPE oid;
if (spelltype->id == OT_S_ANIMATESTATUE) {
oid = OT_STATUE;
} else {
oid = OT_TREE;
}
if (spelllf) *spelllf = NULL;
if (spellob) *spellob = NULL;
// adjacent cell with a tree?
getradiuscells(lf->cell, 1, DT_COMPASS, B_TRUE, LOF_DONTNEED, B_FALSE, cell, &ncells, 0);
for (i = 0; i < ncells; i++) {
if (hasob(cell[i]->obpile, oid)) {
poss[nposs++] = cell[i];
}
}
if (!nposs) {
if (spellcell) spellcell = NULL;
return B_TRUE;
}
if (spellcell) {
*spellcell = poss[rnd(0,nposs-1)];
}
} else if (spelltype->id == OT_A_JUMP) {
cell_t *cell[MAXCANDIDATES],*c;
cell_t *poss[MAXCANDIDATES];
int ncells,i,bestdist,nposs;
if (purpose == F_AICASTTOATTACK) {
bestdist = 99;
} else {
bestdist = -99;
}
getradiuscells(lf->cell, 2, DT_COMPASS, B_TRUE, LOF_WALLSTOP, B_FALSE, cell, &ncells, 0);
for (i = 0; i < ncells; i++) {
int disttovictim;
c = cell[i];
if (!cellwalkable(lf, c, NULL) || celldangerous(lf, c, B_TRUE, NULL)) {
continue;
}
disttovictim = getcelldist(victim->cell, c);
if (purpose == F_AICASTTOATTACK) {
// get closest cell to victim
if (disttovictim < bestdist) {
bestdist = disttovictim;
}
} else {
// get furthest cell from victim
if (disttovictim > bestdist) {
bestdist = disttovictim;
}
}
}
// now get all possible cells...
nposs = 0;
for (i = 0; i < ncells; i++) {
int disttovictim;
c = cell[i];
if (!cellwalkable(lf, c, NULL) || celldangerous(lf, c, B_TRUE, NULL)) {
continue;
}
disttovictim = getcelldist(victim->cell, c);
if (disttovictim == bestdist) {
poss[nposs++] = c;
}
}
// cast spell at one of the cells we found.
// we should ALWAYS have at least one cell, because aispellok()
// will check this.
if (spellcell) *spellcell = poss[rnd(0,nposs-1)];
} else if (spelltype->id == OT_S_DIG) {
cell_t *cell[MAXCANDIDATES],*poss[MAXCANDIDATES];
int ncells,i,nposs = 0;
getradiuscells(lf->cell, 1, DT_COMPASS, B_TRUE, LOF_DONTNEED, B_FALSE, cell, &ncells, 0);
for (i = 0; i < ncells; i++) {
if (cell[i]->type->solid &&
(isdiggable(cell[i], OT_S_DIG) &&
getcelldist(cell[i], victim->cell) == 1)) {
poss[nposs++] = cell[i];
break;
}
}
// aim at an adjacent wall cell
if (spellcell) *spellcell = poss[rnd(0,nposs-1)];
} else if (spelltype->id == OT_S_PLANTWALK) {
cell_t *cell[MAXCANDIDATES];
int ncells,i;
cell_t *poss[MAX_MAPW*MAX_MAPH];
int nposs = 0;
getradiuscells(lf->cell, 1, DT_COMPASS, B_TRUE, LOF_DONTNEED, B_TRUE, cell, &ncells, 0);
// any plants within range 1?
for (i = 0; i < ncells; i++) {
if (hasobofclass(cell[i]->obpile, OC_FLORA)) {
poss[nposs++] = cell[i];
}
}
// should always be true since we check this in aispellok
if (nposs > 0) {
if (spellcell) *spellcell = poss[rnd(0,nposs-1)];
if (spelllf) *spelllf = NULL;
if (spellob) *spellob = NULL;
}
} else if (spelltype->id == OT_S_TELEKINESIS) {
float maxweight;
object_t *poss[MAXPILEOBS];
int nposs;
int i;
// find nearest object which can be picked up
// this is copied out of the telekenesis spell code!
maxweight = getlfweight(lf, B_NOOBS) +
(getlfweight(lf, B_NOOBS) * (getstatmod(lf, A_IQ) / 100));
nposs = 0;
for (i = 0; i < lf->nlos; i++) {
if (lf->los[i] != lf->cell) {
object_t *o;
for (o = lf->los[i]->obpile->first ; o ; o = o->next) {
if (!hasflag(o->flags, F_NOPICKUP) &&
getobweight(o) <= maxweight) {
poss[nposs] = o;
nposs++;
if (nposs >= MAXPILEOBS) break;
}
}
if (nposs >= MAXPILEOBS) break;
}
}
// should always be true since we check this in aispellok
if (nposs > 0) {
if (spellob) *spellob = poss[rnd(0,nposs-1)];
}
// cast spell at the victim
if (spelllf) *spelllf = victim;
if (spellcell) *spellcell = victim->cell;
/*
} else if (spelltype->id == OT_S_CHARM) {
lifeform_t *l;
l = getnearbypeaceful(lf);
if (l) {
if (spelllf) *spelllf = l;
if (spellcell) *spellcell = l->cell;
if (spellob) *spellob = NULL;
}
*/
} else if (spelltype->id == OT_S_SUPERHEAT) {
// get all potions
object_t *poss[MAXPILEOBS],*o;
int nposs = 0;
for (o = lf->pack->first ; o ; o = o->next) {
if (hasflag(o->flags, F_DRINKABLE)) {
poss[nposs++] = o;
}
}
assert(nposs); // aispellok should have checked.
if (spelllf) *spelllf = NULL;
if (spellcell) *spellcell = victim->cell;
if (spellob) *spellob = poss[rnd(0,nposs-1)];
}
}
return B_FALSE;
}
cell_t *aigettargetcell(lifeform_t *lf, flag_t **targflag) {
flag_t *f;
if (targflag) {
*targflag = NULL;
}
f = hasflag(lf->flags, F_TARGETCELL);
if (!f) return NULL;
if (targflag) {
// set this even if the actual cell is invalid.
*targflag = f;
}
return getcellat(lf->cell->map, f->val[0], f->val[1]);
}
object_t *aigetwand(lifeform_t *lf, enum FLAG purpose) {
object_t *o;
object_t *poss[MAXPILEOBS];
int nposs = 0;
for (o = lf->pack->first ; o ; o = o->next) {
// wand with charges left?
if ((o->type->obclass->id == OC_WAND) && (getcharges(o) > 0)) {
// do we know how to use it?
if (canoperate(lf, o, NULL) && hasflag(o->flags, purpose)) {
// TODO: if castatself, check whether we actually need to (ie. healing, invis, etc)
poss[nposs] = o;
nposs++;
}
}
}
if (nposs > 0) {
return poss[rnd(0,nposs-1)];
}
return NULL;
}
// returns targetcell flag on success
flag_t *aigoto(lifeform_t *lf, cell_t *c, enum MOVEREASON why, void *data, int timelimit) {
int db = B_FALSE;
char whybuf[BUFLEN];
flag_t *f = NULL;
if (lfhasflag(lf, F_DEBUG)) {
db = B_TRUE;
}
if (lfhasflagval(lf, F_DOESNTMOVE, NA, NA, B_TRUE, NULL)) return NULL;
if (lfhasflagval(lf, F_IGNORECELL, c->x, c->y, NA, NULL)) {
char lfname[BUFLEN];
getlfname(lf, lfname);
if (db) dblog(".oO { %s cannot go to targecell %d,%d due to f_ignorecell flag }", lfname, c->x, c->y);
return NULL;
}
if (db) {
char lfname[BUFLEN];
getlfname(lf, lfname);
dblog(".oO { %s going to targecell: %d, %d }", lfname, c->x, c->y);
}
// kill previous target flags.
killflagsofid(lf->flags, F_AIPATH);
killflagsofid(lf->flags, F_TARGETLF);
killflagsofid(lf->flags, F_TARGETCELL);
if (why == MR_LF) {
snprintf(whybuf, BUFLEN, "%d", ((lifeform_t *)data)->id);
} else if (why == MR_OB) {
snprintf(whybuf, BUFLEN, "%ld", ((object_t *)data)->id);
} else {
strcpy(whybuf, "");
}
if ((timelimit == PERMENANT) || (timelimit == UNLIMITED)) {
f = addflag(lf->flags, F_TARGETCELL, c->x, c->y, why, whybuf);
} else {
f = addtempflag(lf->flags, F_TARGETCELL, c->x, c->y, why, whybuf,timelimit);
}
return f;
}
int ai_attack_existing_target(lifeform_t *lf) {
lifeform_t *target;
int db = B_FALSE;
enum ATTRBRACKET iqb;
if (lfhasflag(lf, F_DEBUG)) db = B_TRUE;
// do we already have a target we are attacking?
target = gettargetlf(lf);
if (!target) return B_FALSE;
iqb = getattrbracket(getattr(lf, A_IQ), A_IQ, NULL);
if (db) dblog(".oO { i have a target: lfid %d (%s). }", target->id, target->race->name);
// target dead or unconscious?
if (isdead(target) || isunconscious(target)) {
if (db) dblog(".oO { my target is dead/ko'd }", target->id, target->race->name);
loseaitargets(lf);
if (areallies(lf, player) && cantalk(lf)) {
char text[BUFLEN];
real_getlfname(target, text, NULL, B_NOSHOWALL, B_CURRACE);
sayphrase(lf, SP_ALLY_TARGETKILL, SV_SHOUT, NA, text, target);
}
return B_FALSE;
}
// aquatic grabbers will try to drag their prey into the water
if (lfhasflagval(lf, F_GRABBING, target->id, NA, NA, NULL) && isaquatic(lf) ) {
if ( hasobwithflag(lf->cell->obpile, F_DEEPWATER) &&
!hasobwithflag(target->cell->obpile, F_DEEPWATER)) {
// move away!
if (!moveawayfrom(lf, target->cell, DT_ORTH, B_FALSE, B_TRUE, B_ONPURPOSE)) {
return B_TRUE;
}
}
}
// try to move towards them.
if (!aimovetolf(lf, target, B_TRUE)) {
// success
return B_TRUE;
}
return B_FALSE;
}
// i am bored - look for something to do!
int ai_bored(lifeform_t *lf, lifeform_t *master, int icanattack) {
int db = B_FALSE,n,i;
lifeform_t *newtarget = NULL;
flag_t *retflag[MAXCANDIDATES];
int nretflags = 0;
flag_t *f,*enraged;
enraged = lfhasflag(lf, F_RAGE);
if (lfhasflag(lf, F_DEBUG)) db = B_TRUE;
// not attacking anyone in particular
if (db) dblog(".oO { i do not have a target or can't move towards it. }");
// special cases
if (lf->race->id == R_IVYRAPID) {
if (cancast(lf, OT_S_CLONE, NULL)) {
// try to expand
if (!castspell(lf, OT_S_CLONE, lf, NULL, lf->cell, NULL, NULL)) {
// stop reproducing
f = hasflagval(lf->flags, F_CANWILL, OT_S_CLONE, NA, NA, NULL);
if (f) killflag(f);
return B_TRUE;
}
}
}
// monsters will return to their lairs
f = lfhasflag(lf, F_STAYINROOM);
if (f && (f->val[0] != NA)) {
int roomid;
cell_t *where;
roomid = f->val[0];
// find the closest cell of my home room
where = getclosestroomcell(lf, roomid);
// move towards my shop. note that if the player leaves then
// re-enters this map, they will find that we have instantly
// teleported there (see mapentereffects).
if (aigoto(lf, where, MR_OTHER, NULL, PERMENANT)) {
// success
return B_TRUE;
}
}
if (!lfhasflag(lf, F_STUNNED)) {
lifeform_t *hateposs[MAXCANDIDATES],*poss[MAXCANDIDATES];
int nposs = 0, nhateposs = 0;
if (db) dblog(".oO { looking for a target . }");
// look for any hated lfs or enemies
newtarget = NULL;
lf->loslock = B_TRUE;
for (n = 0; n < lf->nlos; n++) {
lifeform_t *who;
if (lf->los[n] != lf->cell) { // not ourself
who = lf->los[n]->lf;
if (who && !isdead(who) && !isunconscious(who) && cansee(lf, who) && isvalidattacktarget(lf, who)) {
int chance = 100; // chance that we ('lf') will attack 'who'
int reachpenalty;
// will usually ignore targets who we can't reach
if (!canreach(lf, who, &reachpenalty) && !aigetrangedattack(lf, who, NULL, NULL)) {
// 1 size too small = 53% chance to attack
// 1 size too small = 6% chance to attack
chance = 100 - (reachpenalty*47);
if (db) dblog(".oO { target %d (%s) is %d sizes out of reach. %d%% chance to ignore }",
who->id, who->race->name,
reachpenalty, reachpenalty*47);
} else if (isresting(who)) {
// targets sleeping in a tent will probably be ignored
object_t *restob;
restob = getrestob(who);
if (restob) {
switch (restob->type->id) {
case OT_TENT: chance = 5; break;
case OT_MOTEL: chance = 0; break;
default: break;
}
}
}
// monsters won't hurt things in the sylvan woods after being
// warned once...
if (lfhasflag(lf, F_SYLVANWARN) &&
(lf->cell->map->region->rtype->id == BH_WOODS) &&
(who->race->raceclass->id == RC_PLANT)) {
chance = 0;
}
if (pctchance(chance)) {
flag_t *f;
if ((getraceclass(lf) == RC_GOD) &&
(getpietylev(lf->race->id, NULL, NULL) <= PL_FURIOUS) &&
isplayer(who)) {
if (nhateposs < MAXCANDIDATES) {
if (db) dblog(".oO { i am an angry god - found player lfid %d (%s) ! }",
who->id, who->race->name);
hateposs[nhateposs++] = who;
}
} else if (lfhasflag(lf, F_HATESALL) || enraged) {
if (nhateposs < MAXCANDIDATES) {
if (db) dblog(".oO { hate everything - found lfid %d (%s) ! }",who->id, who->race->name);
hateposs[nhateposs++] = who;
}
break;
} else if (lfhasflagval(lf, F_HATESRACE, who->race->id, NA, NA, NULL) ||
lfhasflagval(lf, F_HATESRACE, who->race->baseid, NA, NA, NULL) ) {
if ((nhateposs < MAXCANDIDATES) && !areallies(lf, who)) {
if (db) dblog(".oO { found a hated target - lfid %d (%s) ! }",who->id, who->race->name);
hateposs[nhateposs++] = who;
}
break;
} else if (lfhasflagval(lf, F_HATESRACECLASS, who->race->raceclass->id, NA, NA, NULL) ) {
if ((nhateposs < MAXCANDIDATES) && !areallies(lf, who)) {
if (db) dblog(".oO { found a hated raceclass target - lfid %d (%s) ! }",who->id, who->race->name);
hateposs[nhateposs++] = who;
}
break;
} else if ( ((f = lfhasflag(lf, F_TERRITORIAL)) != NULL) &&
(getcelldist(who->cell, lf->cell) <= f->val[0]) ) {
if ((nhateposs < MAXCANDIDATES) && !areallies(lf, who)) {
if (db) dblog(".oO { territorial and found target in range - lfid %d (%s) ! }",who->id, who->race->name);
hateposs[nhateposs++] = who;
}
break;
} else if (!nhateposs && areenemies(lf, who)) { // dont check if we've already found a hated target
if (nposs < MAXCANDIDATES) {
if (db) dblog(".oO { found an enemy target - lfid %d (%s) ! }",who->id, who->race->name);
poss[nposs++] = who;
}
} else {
getflags(lf->flags, retflag, &nretflags, F_HATESRACEWITHFLAG, F_NONE);
for (i = 0; i < nretflags; i++) {
if (lfhasflagval(who, retflag[i]->val[0], retflag[i]->val[1], retflag[i]->val[2], NA, NULL) &&
!areallies(lf, who)) {
if (db) dblog(".oO { found a target with hated flags - lfid %d (%s) ! }",who->id, who->race->name);
hateposs[nhateposs++] = who;
}
}
}
}
}
}
}
lf->loslock = B_FALSE;
if (nhateposs) {
newtarget = hateposs[rnd(0,nhateposs-1)];
} else if (nposs) {
newtarget = poss[rnd(0,nposs-1)];
}
}
if (newtarget) {
if (aiattack(lf, newtarget, aigetchasetime(lf))) {
// failed for some reason. maybe target was feigning
// death?
if (db) dblog(".oO { setting a new target via aiattack failed! }");
} else {
// then move towards them...
if (db) dblog(".oO { moving towards my new target (%d,%d) -> (%d,%d) }", lf->cell->x, lf->cell->y,
newtarget->cell->x, newtarget->cell->y);
if (icanattack) {
if (!movetowards(lf, newtarget->cell, DT_ORTH, B_FALSE)) {
turntoface(lf, newtarget->cell);
return B_TRUE;
}
} else {
if (db) dblog(".oO { won't move towards target - i have no weapon. }");
}
}
} else {
// god with no targets?
if (lf->race->raceclass->id == RC_GOD) {
if (onein(3) && (lf->cell->map->habitat->id != H_HEAVEN)) {
if (db) dblog(".oO { planeshifting home }");
// gods will planeshift away
if (!castspell(lf, OT_S_PLANESHIFT, lf, NULL, lf->cell, NULL, NULL)) {
return B_TRUE;
}
}
}
}
///////////////////////////////////////////////
// training
///////////////////////////////////////////////
// need to train skills?
if (!enraged) {
if (readytotrain(lf) && safetorest(lf, NULL)) {
// special case - monsters don't need to actually rest to gain
// skills, although they DO still need to wait until the player
// is out of sight.
enhanceskills(lf);
}
// do we have armour which needs repairing?
if (getskill(lf, SK_ARMOUR) >= PR_SKILLED) {
if (db) dblog(".oO { do i have any armour to repair? }");
// just try to use the ability - it'll fail if we have nothing
// which we can repair.
if (!useability(lf, OT_A_REPAIR, NULL, NULL)) {
if (db) dblog(".oO { yes - done. }");
// success
return B_TRUE;
} else {
if (db) dblog(".oO { no armour to repair. }");
}
}
// pet movement - note that pets will only rest if their
// master is resting. the normal rest code underneath this section
// will never be called.
if (master) {
//lifeform_t *master;
//master = findlf(lf->cell->map, mf->val[0]);
if (!aimovetolf(lf, master, B_FALSE)) {
// success
return B_TRUE;
}
}
}
///////////////////////////////////////////////
// resting / healing
///////////////////////////////////////////////
if (!enraged) {
// hide?
if (!ispetof(lf, player) && cancast(lf, OT_A_HIDE, NULL) && aispellok(lf, OT_A_HIDE, lf, F_AICASTTOFLEE)) {
if (db) dblog(".oO { trying to hide. }");
if (!useability(lf, OT_A_HIDE, lf, lf->cell)) {
if (db) dblog(".oO { success! }");
return B_TRUE;
}
}
// need to heal?
if (needstorest(lf, NULL) && !hasflag(lf->flags, F_RAGE) && !lfhasflag(lf, F_NORESTHEAL)) {
enum ERROR why;
if (safetorest(lf, &why) || (why == E_TOOSOON)) {
if (db) dblog(".oO { resting to heal }");
rest(lf, B_TRUE);
return B_TRUE;
}
}
// pretending to be an object? revert.
f = lfhasflag(lf, F_PRETENDSTOBE);
if (f) {
if (safetorest(lf, NULL)) {
object_t *oo;
oo = addobfast(lf->cell->obpile, f->val[0]);
if (oo) {
killflagsofid(oo->flags, F_SIZE);
addflag(oo->flags, F_SIZE, getlfsize(lf), NA, NA, NULL);
// replace contents object
if (oo->contents->first && strlen(f->text)) {
killob(oo->contents->first);
addob(oo->contents, f->text);
}
// kill original monster
addflag(lf->flags, F_NODEATHANNOUNCE, B_TRUE, NA, NA, NULL);
addflag(lf->flags, F_NOCORPSE, B_TRUE, NA, NA, NULL);
addflag(lf->flags, F_XPVAL, 0, NA, NA, NULL);
lf->hp = 0;
}
}
}
}
return B_FALSE;
}
// returns true if we did somethign
int ai_handle_emergencies(lifeform_t *lf, enum ATTRBRACKET iqb) {
int db = B_FALSE;
int moveawayfromcell = B_FALSE;
flag_t *f;
if (lfhasflag(lf, F_DEBUG)) db = B_TRUE;
if (lfhasflag(lf, F_RAGE)) return B_FALSE;
// if we are not near our life ob, and not moving towards it, do so!
f = lfhasflag(lf, F_LIFEOB);
if (f) {
if (!findnearbylifeob(lf->cell, NA, f, NULL)) {
cell_t *poss[8];
int nposs = 0;
int dir;
cell_t *targcell;
// target cell near our life ob? if so, we're okay.
targcell = aigettargetcell(lf, NULL);
if (targcell && findnearbylifeob(targcell, NA, f, NULL)) {
} else {
if (db) dblog(".oO { not near my lifeob! }");
for (dir = DC_N; dir <= DC_NW; dir++) {
cell_t *c;
c = getcellindir(lf->cell, dir);
if (c && findnearbylifeob(c, NA, f, NULL)) {
poss[nposs++] = c;
}
}
if (nposs) {
cell_t *c;
c = poss[rnd(0,nposs-1)];
if (aigoto(lf, c, MR_OTHER, NULL, PERMENANT)) {
if (db) dblog(".oO { moving closer to lifeob }");
// success
return B_TRUE;
} else {
if (db) dblog(".oO { couldnt find an adjacent cell near lifeob }");
// TODO: search all in los
}
}
}
}
}
// if our cell is dangerous, move away!
if (iqb >= AT_AVERAGE) {
if (celldangerous(lf, lf->cell, B_TRUE, NULL)) {
if (db) dblog("%s o O { there is something dangerous here, moving away } ", lf->race->name);
moveawayfromcell = B_TRUE;
}
}
// hot feet? move somewhere.
if (!moveawayfromcell && lfhasflag(lf, F_HOTFEET) && !isimmuneto(lf->flags, DT_FIRE, B_FALSE)) {
if (db) dblog("%s o O { i have f_hotfeet, moving away } ", lf->race->name);
moveawayfromcell = B_TRUE;
}
if (moveawayfromcell) {
if (!dorandommove(lf, B_NOBADMOVES, B_FALSE, B_TRUE)) {
return B_TRUE;
}
}
// flying monsters not flying?
if (!isprone(lf)) {
if (hasflag(lf->race->flags, F_NATURALFLIGHT) && !lfhasflag(lf, F_FLYING)) {
if (cancast(lf, OT_A_FLY, NULL) && !isburdened(lf)) {
if (lfhasflag(lf, F_NOSTAM) || (getstaminapct(lf) >= 80)) {
if (!useability(lf, OT_A_FLY, lf, lf->cell)) {
return B_TRUE;
}
}
}
}
if (hasflag(lf->race->flags, F_LEVITATING) && !lfhasflag(lf, F_LEVITATING)) {
copyflag(lf->flags, lf->race->flags, F_LEVITATING);
taketime(lf, getmovespeed(lf));
return B_TRUE;
}
}
// flying and out of stamina?
if (isflyingwithwings(lf) && isexhausted(lf)) {
// stop flying.
if (!useability(lf, OT_A_FLY, lf, lf->cell)) {
return B_TRUE;
}
}
return B_FALSE;
}
int ai_healing(lifeform_t *lf) {
int db = B_FALSE;
if (lfhasflag(lf, F_DEBUG)) db = B_TRUE;
if (lfhasflag(lf, F_RAGE)) return B_FALSE;
// special cases
// - mosquitoids and leechs will sleep when satiated.
if ((lf->race->id == R_STIRGE) || (lf->race->id == R_LEECH)) {
if (db) dblog(".oO { i am satiated. going to sleep. }");
if (ispeaceful(lf) && !isundead(lf)) {
int sleepval = 18;
if (modcounter(lf->flags, 1) >= sleepval) {
// we say that this ISNT on purpose, because otherwise
// we'll wake up as soon as we're healed. in this case
// we actually want to sleep forever (or until woken).
if (!gotosleep(lf, B_FALSE)) {
// force this since when not on purpose, gotosleep wont
// take time.
taketime(lf, getactspeed(lf));
return B_TRUE; // success
}
}
}
}
// feigning death with enemies in sight, and hurt?
if (lfhasflag(lf, F_FEIGNINGDEATH) && !safetorest(lf, NULL)) {
if (islowhp(lf)) {
if (db) dblog(".oO { i am feigning death and bleeding (hp=%d/%d), skipping turn. }",lf->hp,lf->maxhp);
// just wait...
rest(lf, B_TRUE);
return B_TRUE;
}
}
// hurt gods planeshift away
if (lf->race->raceclass->id == RC_GOD) {
if (gethppct(lf) <= 10) {
if (!castspell(lf, OT_S_PLANESHIFT, lf, NULL, lf->cell, NULL, NULL)) {
return B_TRUE;
}
}
}
// need to heal?
if (lf->hp < (lf->maxhp/2)) {
if (!useitemwithflag(lf, F_AIHEALITEM)) {
return B_TRUE;
} else if (cansleep(lf)) {
// don't have or can't use our healing items
// no enemies in sight?
if (safetorest(lf, NULL)) {
// gods will only sleep/meditate if they are in the realm of gods
if (isgod(lf) && (lf->cell->habitat->id != H_HEAVEN)) {
} else {
// if it's "night time" for us, sleep forever.
// otehrwise just sleep until we're healed
if (!gotosleep(lf, issleepingtimefor(lf) ? B_TRUE : B_FALSE)) {
taketime(lf, getactspeed(lf)); // to make sure our turn ends
return B_TRUE; // success
}
}
}
}
}
// no stamina left?
if (isexhausted(lf) && !isinbattle(lf, B_FALSE, B_FALSE)) {
rest(lf, B_TRUE);
return B_TRUE;
}
return B_FALSE;
}
int ai_housekeeping(lifeform_t *lf, lifeform_t *master) {
int i;
flag_t *f;
int db = B_FALSE;
if (lfhasflag(lf, F_DEBUG)) db = B_TRUE;
if (lfhasflag(lf, F_RAGE)) return B_FALSE;
if (lfhasflag(lf, F_ISPRISONER) && master && isplayer(master) &&
(cansee(lf, master) || isadjacent(lf->cell,master->cell))) {
// soon after escaping the dungeon, they'll leave you.
if (isoutdoors(lf->cell->map) && pctchance(85)) {
object_t *o;
say(lf, "Thanks for getting me out!", SV_TALK);
o = addobfast(master->pack, OT_MANUAL);
if (o) {
char obname[BUFLEN];
say(lf, "Here, let me teach you something as a reward.", SV_TALK);
getobname(o, obname, o->amt);
msgnocap("%c - %s", o->letter, obname);
}
// no longer an ally
killflagsofid(lf->flags, F_FRIENDLY);
killflagsofid(lf->flags, F_PETOF);
killflagsofid(lf->flags, F_ISPRISONER);
}
}
// too many objects?
i = countobs(lf->pack, B_FALSE);
if (i >= (MAXPILEOBS - 5)) {
object_t *container;
// get largest container with space
container = getbestcontainer(lf->pack);
if (container) {
object_t *o;
// find object which will fit
for (o = lf->pack->first ; o ; o = o->next) {
if ((o != container) && !isequipped(o) && obfits(o, container->contents)) {
// put it in.
moveob(o, container->contents, ALL);
if (cansee(player, lf)) {
char obname[BUFLEN];
char lfname[BUFLEN];
char containername[BUFLEN];
// announce
getobname(o, obname, o->amt);
getobname(container, containername, 1);
getlfname(lf, lfname);
msg("%s puts %s into %s.", lfname, obname, containername);
}
// timetime
taketime(lf, getactspeed(lf));
return B_TRUE;
}
}
}
}
// talking
f = lfhasflag(lf, F_RANDOMTALKPCT);
if (f) {
if (pctchance(f->val[0])) {
flag_t *poss[MAXCANDIDATES];
int nposs = 0,i;
flag_t *retflag[MAXCANDIDATES];
int nretflags = 0;
getflags(lf->flags, retflag, &nretflags, F_RANDOMTALK, F_NONE);
for (i = 0; i < nretflags; i++) {
poss[nposs++] = retflag[i];
}
if (nposs) {
int vol;
f = poss[rnd(0,nposs-1)];
vol = rnd(f->val[1], f->val[2]);
if (strlen(f->text)) {
say(lf, f->text, vol);
} else {
sayphrase(lf, f->val[0], vol, NA, NULL, NULL);
}
}
}
}
return B_FALSE;
}
int ai_inventory_mgt(lifeform_t *lf, int *canattack) {
int db = B_FALSE,i;
object_t *curwep = NULL,*bestwep = NULL, *o = NULL;
object_t *curgun = NULL,*bestgun = NULL;
enum BODYPART bp;
int icanattack = B_FALSE;
enum ATTRBRACKET iqb;
iqb = getattrbracket(getattr(lf, A_IQ), A_IQ, NULL);
if (lfhasflag(lf, F_DEBUG)) db = B_TRUE;
if (lfhasflag(lf, F_RAGE)) return B_FALSE;
// burdened?
if (isburdened(lf) && (iqb >= IQ_ANIMAL)) {
object_t *o,*heaviest = NULL;
float hevweight = 0;
if (db) dblog(".oO { i am burdened }");
// drop our heaviest non-equipped object
for (o = lf->pack->first ; o ; o = o->next) {
if (!isequipped(o)) {
float thisweight;
thisweight = getobweight(o);
if (thisweight > hevweight) {
hevweight = thisweight;
heaviest = o;
}
}
}
if (heaviest) {
if (db) {
char obname[BUFLEN];
getobname(o, obname, ALL);
dblog(".oO { i will drop %s to lower my burden }", obname);
}
if (!drop(heaviest, ALL)) {
return B_TRUE;
}
if (db) dblog(".oO { drop failed! }");
}
if (db) dblog(".oO { couldn't drop anything }");
}
// do we have a better weapon we could use?
if (iqb >= AT_AVERAGE) {
curwep = getweapon(lf);
bestwep = getbestweapon(lf);
if ((curwep != bestwep) && !isfirearm(curwep)) {
if (db) dblog(".oO { i have a better weapon than my current one (%s > %s) }",bestwep->type->name, curwep ? curwep->type->name : "nothing");
// weild better one
if (!weild(lf, bestwep)) return B_TRUE;
}
// do we have a better firearm ?
curgun = getfirearm(lf);
if (curwep && istwohandedfor(curwep, lf)) {
// we are using a two handed weapon. don't
// check for guns.
} else {
bestgun = getbestfirearm(lf);
if (curgun != bestgun) {
if (db) dblog(".oO { i have a better gun than my current one (%s > %s) }",bestgun->type->name, curgun ? curgun->type->name : "nothing");
// weild better one
if (!weild(lf, bestgun)) return B_TRUE;
}
}
// do we have ammo for an empty gun?
if (curgun) {
object_t *curammo;
curammo = getammo(curgun);
if (!curammo) {
o = getrandomammo(lf);
if (o && !loadfirearm(lf, curgun, o)) {
// success
return B_TRUE;
}
}
}
}
if (iqb >= AT_GTAVERAGE) {
// do we have better armour?
for (i = 0; i < lf->race->nbodyparts; i++) {
object_t *curarm;
bp = lf->race->bodypart[i].id;
curarm = getarmour(lf, bp);
// is it red hot?
if (curarm && hasflag(curarm->flags, F_HOT) && !isimmuneto(lf->flags, DT_FIRE, B_FALSE)) {
if (db) dblog("%s o O { wearing a red-hot item. will try to remove it. } ", lf->race->name);
if (cantakeoff(lf, curarm, NULL)) {
if (!takeoff(lf, curarm)) {
return B_TRUE;
}
} else {
if (db) dblog("%s o O { cannot remove it. maybe cursed? } ", lf->race->name);
}
}
// do we have a better one?
for (o = lf->pack->first ; o ; o = o->next) {
if (!isdangerousob(o, lf, B_TRUE) && canwear(lf, o, bp) && isbetterarmourthan(o, curarm)) {
// wear this armour instead
if (!wear(lf, o)) return B_TRUE;
}
}
}
}
// now check whetehr we have ANY weapon
if (curwep || lfhasflag(lf, F_HASATTACK)) {
icanattack = B_TRUE;
if (canattack) *canattack = B_TRUE;
}
// before attacking targets,
// look for any object which we _covet_.
// ie. if we covet something, we will pick it up
// instead of attacking our target.
if (!lfhasflag(lf, F_HIDING) && !lfhasflag(lf, F_FEIGNINGDEATH)) {
if (db) dblog(".oO { looking for covetted objects... }");
if (lookforobs(lf)) {
if (db) dblog(".oO { found covetted object. returning. }");
return B_TRUE;
}
}
return B_FALSE;
}
int ai_movement(lifeform_t *lf) {
int valid = B_TRUE;
cell_t *c;
int db = B_FALSE;
flag_t *f = NULL;
if (lfhasflag(lf, F_DEBUG)) db = B_TRUE;
if (lfhasflagval(lf, F_DOESNTMOVE, NA, NA, B_TRUE, NULL)) return B_FALSE;
if (isimmobile(lf)) return B_FALSE;
// do we have a target cell?
c = aigettargetcell(lf, &f);
if (!f) return B_FALSE;
// is it still valid?
if (!c) {
valid = B_FALSE;
} else if (f->val[2] == MR_LF) {
lifeform_t *who;
who = findlf(lf->cell->map, atoi(f->text));
// lf doesn't exist?
if (!who) {
valid = B_FALSE;
} else if (cansee(lf, who) && (lf->cell != c)) {
// can see them and they're not where we are going?
valid = B_FALSE;
}
} else if (f->val[2] == MR_OB) {
object_t *what;
what = findobidinmap(lf->cell->map, atol(f->text));
if (!what) {
valid = B_FALSE;
} else if (haslos(lf, c) && (what->pile->where != c)) {
// if you can see the cell and object isn't there anymore
valid = B_FALSE;
} else if (c->lf && !areenemies(lf, c->lf) && haslos(lf, c) && (getcelldist(lf->cell, c) == 1)) {
// can see a non-enemy on top of the object, and we are adjacent
valid = B_FALSE;
}
} else if (f->val[2] == MR_SOUND) {
// always ok.
} else if (f->val[2] == MR_BACKTOLAIR) {
// always ok.
} else {
// weird ?
raise (SIGINT);
}
if (valid) {
return aimovetotargetcell(lf, f);
} else {
killflag(f);
}
return B_FALSE;
}
int ai_premovement(lifeform_t *lf) {
int db = B_FALSE;
int i;
if (lfhasflag(lf, F_DEBUG)) db = B_TRUE;
if (lfhasflag(lf, F_RAGE)) return B_FALSE;
// need light?
if (lfproduceslight(lf, NULL) && (lf->cell->map->illumination != IL_FULLLIT)) {
object_t *lamp;
lamp = hasobwithflagval(lf->pack, F_ACTIVATECONFER, F_PRODUCESLIGHT, NA, NA, NULL);
if (lamp && !isactivated(lamp) && canoperate(lf, lamp, NULL)) {
if (db) dblog(".oO { it's dark and i have an inactive light source (%s) }", lamp->type->name);
if (!operate(lf, lamp, NULL)) {
if (db) dblog(".oO { successfully turned it on. }");
return B_TRUE;
} else {
if (db) dblog(".oO { failed to turn it on. }");
}
}
}
// ally needs ammo?
lf->loslock = B_TRUE;
for (i = 0; i < lf->nlos; i++) {
cell_t *c;
c = lf->los[i];
if (c->lf && (c->lf != lf) && areallies(lf, c->lf)) {
object_t *gun;
gun = getfirearm(c->lf);
if (gun && !getammo(gun)) {
object_t *o;
for (o = lf->pack->first ; o ; o = o->next) {
if (isammofor(o->type, gun) ) {
if (getcelldist(lf->cell, c) <= getmaxthrowrange(lf, o)) {
// throw it to them!
if (!throwat(lf, o, c)) {
// success
return B_TRUE;
}
}
}
}
}
}
}
lf->loslock = B_FALSE;
return B_FALSE;
}
flag_t *aihastarget(lifeform_t *lf) {
flag_t *f;
f = lfhasflag(lf, F_TARGETLF);
if (f) return f;
f = lfhasflag(lf, F_TARGETCELL);
if (f) return f;
return NULL;
}
// returns B_FALSE if we did something.
// returns B_TRUE if we failed (ie. did nothing)
int aimovetolf(lifeform_t *lf, lifeform_t *target, int wantattack) {
int db = B_FALSE;
int ismaster = B_FALSE;
flag_t *targetflag = NULL;
if (lfhasflag(lf, F_DEBUG)) db = B_TRUE;
if (db) {
dblog(".oO { starting aimovetolf }");
}
targetflag = lfhasflagval(lf, F_PETOF, target->id, NA, NA, NULL);
if (targetflag) {
ismaster = B_TRUE;
} else {
targetflag = lfhasflagval(lf, F_TARGETLF, target->id, NA, NA, NULL);
}
if (cansee(lf, target)) {
int dist,wantdistmin,wantdistmax;
int attackok;
flag_t *f;
enum OBTYPE spell;
enum RANGEATTACK rangedattack = RA_NONE;
int shootrange = 0;
int movefailed = B_FALSE;
int closethrowok = B_FALSE;
object_t *rangedob = NULL;
int spellchance = 0;
if (db) dblog(".oO { can see my target }");
// see if we have a ranged attack. if so, adjust wantdist
// to maintain distance.
rangedob = aigetrangedattack(lf, target, &rangedattack, &shootrange);
// how far away is my target ?
dist = getcelldist(lf->cell, target->cell);
// try spells first.
// can we attack with spells (ie. ones which target the victim)?
// if target is adjacent, we will normally just attack rather than try a spell.
// random chance of casting a spell
f = lfhasflag(lf, F_CASTCHANCE);
if (f) spellchance = f->val[0];
else spellchance = 30;
// some attacks can always happen
if (cancast(lf, OT_A_THRUST, NULL) && (dist == 2) && haslofknown(lf->cell, target->cell, LOF_NEED, NULL)) {
spellchance = 100;
}
if (pctchance(spellchance)) {
spell = aigetattackspell(lf, target);
} else {
spell = OT_NONE;
}
// pet movement
if (ismaster) {
if (isresting(target)) {
// rest as well.
rest(lf, B_TRUE);
return B_FALSE;
} else if (isadjacent(lf->cell, target->cell)) {
if (db) dblog(".oO { i can see my master adjacent - moving randomly }");
// move randomly. TODO: just return ??
dorandommove(lf, B_NOBADMOVES, B_TRUE, B_FALSE);
return B_FALSE;
}
// otherwise fall through to below movement code.
}
// how far away do i _want_ to be?
getwantdistance(lf,target, &wantdistmin,&wantdistmax, wantattack);
// reset F_TARGET lifetime to full.
if (targetflag) {
if (targetflag->id == F_TARGETLF) {
targetflag->lifetime = aigetchasetime(lf);
}
if (db) dblog(".oO { i can see my target (at %d,%d). might move towards it. }",target->cell->x,target->cell->y);
// remember their location
targetflag->val[1] = target->cell->x;
targetflag->val[2] = target->cell->y;
}
// is an attack possible?
attackok = B_FALSE;
if (wantattack) {
if (dist == 1) {
attackok = B_TRUE;
} else if (!lfhasflag(lf, F_HIDING) || rangedob || (spell != OT_NONE)) {
attackok = B_TRUE;
} else if (!lfhasflag(lf, F_FEIGNINGDEATH)) {
attackok = B_TRUE;
}
}
if (attackok) {
objecttype_t *st;
// drink boost potions
if (!useitemwithflag(lf, F_AIBOOSTITEM)) {
return B_FALSE;
}
st = findot(spell);
if ( (spell != OT_NONE) && // found a valid spell/ability to use
// AND one of these:
((dist != 1) || // there is distance between us and target
(getspellrange(lf, st->id, 1, NULL) == 1) || // OR this works from adjacent
(st->obclass->id == OC_ABILITY) || // OR this is an ability
!countinnateattacks(lf) || // OR we have no melee attack
(rnd(1,3) == 1)) // OR random chance of using anyway...
) {
int spellfailed = B_FALSE;
lifeform_t *spelllf = NULL;
cell_t *spellcell = NULL;
object_t *spellob = NULL;
if (db) {
dblog(".oO { will cast attack spell: %s }", st->name);
}
// special cases: eg. spells like telekenesis
if (spell == OT_S_TELEKINESIS) {
float maxweight;
object_t *poss[MAXPILEOBS];
int nposs;
int i;
// find nearest object which can be picked up
// this is copied out of the telekenesis spell code!
maxweight = getlfweight(lf, B_NOOBS) +
(getlfweight(lf, B_NOOBS) * (getstatmod(lf, A_IQ) / 100));
nposs = 0;
for (i = 0; i < lf->nlos; i++) {
if (lf->los[i] != lf->cell) {
object_t *o;
for (o = lf->los[i]->obpile->first ; o ; o = o->next) {
if (!hasflag(o->flags, F_NOPICKUP) &&
getobweight(o) <= maxweight) {
poss[nposs] = o;
nposs++;
if (nposs >= MAXPILEOBS) break;
}
}
if (nposs >= MAXPILEOBS) break;
}
}
if (nposs > 0) {
spellob = poss[rnd(0,nposs-1)];
} else {
spellfailed = B_TRUE;
}
// cast spell at the player
spelllf = target;
spellcell = target->cell;
} else {
// pick targets based on spell flags
if (aigetspelltarget(lf, st, target, &spelllf, &spellcell, &spellob, F_AICASTTOATTACK)) {
spellfailed = B_TRUE;
}
}
if (spellfailed) {
if (db) dblog(".oO { cast spell/ability failed (1)! }");
} else {
if (getschool(spell) == SS_ABILITY) {
spellfailed = useability(lf, spell, spelllf, spellcell);
} else {
spellfailed = castspell(lf, spell, spelllf, spellob, spellcell, NULL, NULL);
}
}
if (spellfailed) {
if (db) dblog(".oO { cast spell/ability tried but failed (2)! reason = %d }", reason);
// spell failed. we will keep going through aiturn.
} else {
// spell succesful
if ((spell == OT_A_STEAL) && !lfhasflag(lf, F_NOFLEE)) {
if (!isgod(lf)) {
// run away for a while
fleefrom(lf, spelllf, rnd(10,20), B_TRUE);
}
}
if ((spell == OT_A_DRAGUNDERGROUND) && (lf->race->id == R_HECTASSERVANT)) {
lf->hp = 0;
}
return B_FALSE;
}
}
// for firearms/projectiles, chance to fire/throw depends on accuracy.
if ((rangedattack == RA_GUN) || (rangedattack == RA_THROW)) {
int chance;
if (lfhasflag(lf, F_WILLTHROW)) {
chance = 100;
} else {
int acc;
acc = getmissileaccuracy(lf, target->cell, getammo(rangedob), rangedob, NULL);
switch (getpctletter(acc,100)) {
case 'S':
case 'A':
chance = 100; break;
case 'B':
chance = 75; break;
case 'C':
chance = 50; break;
default:
chance = 25; break;
}
}
if (!pctchance(chance)) {
rangedattack = RA_NONE;
rangedob = NULL;
}
}
if (rangedattack != RA_NONE) { // ie if we found a ranged attack
if ((rangedattack == RA_THROW) && rangedob && hasflag(rangedob->flags, F_POWDER)) {
// don't have to maintain distance to throw powder
closethrowok = B_TRUE;
if (wantdistmin < 1) wantdistmin = 1;
if (wantdistmax < wantdistmin) wantdistmax = shootrange;
} else {
// stay out of target's attack range
if (wantdistmin < 2) wantdistmin = 2;
// and stay within range for our ranged attack
if (wantdistmax < wantdistmin) wantdistmax = shootrange;
}
}
} // end if attackok
// move towards the target lf.
// try to get to our ideal range from them.
if (db) dblog(".oO { i am at distance %d, want to be at %d-%d }", dist, wantdistmin, wantdistmax);
if (dist > wantdistmax) {
// want to move but our race doesn't move?
if (lfhasflag(lf, F_DOESNTMOVE)) {
if (db) dblog(".oO { want to move towards target but have f_doesntmove - abandoning target. }");
loseaitargets(lf);
return B_TRUE;
}
if (db) dblog(".oO { moving towards target. }");
// do we need to sprint got catch up?
if (lfhasflag(target, F_SPRINTING) && !lfhasflag(lf, F_SPRINTING) && cancast(lf, OT_A_SPRINT, NULL)) {
useability(lf, OT_A_SPRINT, NULL,NULL); // doesn't matter if it fails
}
if (!movetowards(lf, target->cell, DT_ORTH, B_FALSE)) {
if (db) dblog(".oO { successfully moved towards target. }");
turntoface(lf, target->cell);
// success
return B_FALSE;
} else {
if (db) dblog(".oO { move towards failed! - reason = %d }",reason);
movefailed = B_TRUE;
}
} else if (dist < wantdistmin) {
if (db) dblog(".oO { moving away from target to maintain mindist %d. }", wantdistmin);
if (!moveawayfrom(lf, target->cell, DT_ORTH, B_KEEPLOF, B_TRUE, B_ONPURPOSE)) { // maintain LOF, and keep facing the target
// success
return B_FALSE;
} else {
if (db) dblog(".oO { move towards failed! - reason = %d }",reason);
movefailed = B_TRUE;
}
}
// if we got here, we're either at the correct distance or couldn't
// move.
if (attackok) {
// if not adjacent, check for guns, wands, throwing
if ( (rangedattack != RA_NONE) && // we have a ranged attack
haslofknown(lf->cell, target->cell, LOF_NEED, NULL) && // and we have line of fire to them
(closethrowok || onein(2) || (getcelldist(lf->cell, target->cell) > 1) )) { // and we're not adjacent to target OR random
if (rangedattack == RA_GUN) {
setguntarget(lf, target);
if (!shoot(lf)) {
// succesful
return B_FALSE;
} else {
if (db) dblog(".oO { shoot gun failed! reason = %d }", reason);
}
} else if (rangedattack == RA_THROW) {
// try to throw it!
if (!throwat(lf, rangedob, target->cell)) {
// succesful
return B_FALSE;
} else {
if (db) dblog(".oO { throw failed! }");
}
} else if (rangedattack == RA_WAND) {
int wandfailed = B_FALSE;
objecttype_t *st;
cell_t *zapcell = NULL;
st = getlinkspell(rangedob);
if (st) {
enum FLAG purpose;
if (lfhasflag(lf, F_FLEEFROM)) {
purpose = F_AICASTTOFLEE;
} else {
purpose = F_AICASTTOATTACK;
}
if (aigetspelltarget(lf, st, target, NULL, &zapcell, NULL, purpose)) {
wandfailed = B_TRUE;
}
} else {
// no linkspell - just zap it.
zapcell = NULL;
}
// zap it
if (!wandfailed) {
if (!operate(lf, rangedob, zapcell)) {
// succesful
return B_FALSE;
} else {
if (db) dblog(".oO { zap failed! }");
}
}
}
} // end if rangedattackok
} // end if attackok
// if we could see our traget, but everything we tried failed (spells, moving and ranged attack),
// either rest or move randomly.
if (movefailed) {
makenoise(lf, N_FRUSTRATED);
if (onein(2)) {
rest(lf, B_TRUE);
} else {
if (dorandommove(lf, B_NOBADMOVES, B_FALSE, B_FALSE)) {
rest(lf, B_TRUE);
}
}
return B_FALSE;
}
} else {
// can't see target.
// move towards their last known location instead
cell_t *targcell;
int lastx,lasty;
int lastdir = D_NONE;
if (db) dblog(".oO { CANNOT see my target }");
targcell = aigetlastknownpos(lf, target, &lastx, &lasty, &lastdir);
if (targcell) {
// are we already AT their last known location?
if (targcell == lf->cell) {
if (db) dblog(".oO { cannot see target. i am already at their last known loc %d/%d }",lastx, lasty);
// go in their last known direction.
if (lastdir == D_NONE) {
if (db) dblog(".oO { i don't know my target's last known movement dir. }");
} else {
// try going in last known dir
if (db) dblog(".oO { trying target's last known move dir (%s) }",getdirname(lastdir));
if (!trymove(lf, lastdir, B_TRUE, B_FALSE)) {
if (db) dblog(".oO { ...successfully }");
// we now don't know their last known dir.
if (targetflag) {
changeflagtext(targetflag, "");
}
return B_FALSE;
}
}
} else {
// not already at their last known cell. try to go there.
if (db) dblog(".oO { i cannot see my target. moving to last known loc %d/%d }",lastx,lasty);
assert(targcell->x == lastx);
assert(targcell->y == lasty);
if (aigoto(lf, targcell, MR_LF, target, PERMENANT)) {
if (db) dblog(".oO { set target cell for LKL. }");
// success
return B_FALSE;
} else {
if (db) dblog(".oO { aigoto target's last known loc failed! }");
}
}
} else {
// we don't know their last known location....
if (db) dblog(".oO { go to target's last known loc failed! }");
}
}
if (db) dblog(".oO { aimovetolf failed. }");
// FAILED.
return B_TRUE;
}
int aimovetotargetcell(lifeform_t *lf, flag_t *f) {
int x,y;
cell_t *c = NULL,*origc,*targetc;
int db = B_FALSE;
enum ATTRBRACKET iqb;
iqb = getattrbracket(getattr(lf, A_IQ), A_IQ, NULL);
checkflagpile_maplfs(lf->cell->map);
if (lfhasflag(lf, F_DEBUG)) {
db = B_TRUE;
}
checkflagpile(lf->flags); // debug
x = f->val[0];
y = f->val[1];
if (db) dblog(".oO { walking from %d,%d towards f_targetcell (%d,%d) ... }", lf->cell->x, lf->cell->y, x, y);
origc = lf->cell;
targetc = getcellat(lf->cell->map, x, y);
if (targetc) {
flag_t *pathf = NULL;
if (!haslof(lf->cell, targetc, LOF_WALLSTOP, NULL)) {
// if we DONT have LOF to the target cell, use
// a pathfinding algorithm.
// do we ahv an existing path?
pathf = lfhasflag(lf, F_AIPATH);
if (!pathf) {
if (iqb >= IQ_ANIMAL) {
// if we DONT have a direct path, then pathfind.
pathf = ai_createpathto(lf, targetc);
}
}
if (pathf) {
c = ai_getnextcellinpath(lf);
} else {
// couldn't find a path there...
// just try to move directly towards it.
}
} else {
// if we _DO_ have lof to the cell, we'll just walk direclty towards it.
c = targetc;
}
if (c) {
// try to move towards the cell
if (movetowards(lf, c, DT_ORTH, B_FALSE )) {
// couldn't move towards it for some reason.
// so stop trying.
if (db) dblog(".oO { couldn't walk towards f_targetcell. abandoning it. }");
loseaitargets(lf);
// remember NOT to target this one.
addignorecell(lf, c);
c = NULL;
} else {
int turned = B_FALSE;
if (lf->cell == origc) {
if (db) dblog(".oO { turned to face f_targetcell. (still at %d,%d) }",lf->cell->x, lf->cell->y);
turned = B_TRUE;
} else {
if (db) dblog(".oO { successfully walked towards f_targetcell. arrived at %d,%d }",lf->cell->x, lf->cell->y);
}
if (pathf && (lf->cell == c)) {
ai_popnextcellinpath(lf);
}
assert(f->id <= F_LAST);
// moved towards it.
// reset lifetime
f->lifetime = aigetchasetime(lf);
assert(f->id <= F_LAST);
// are we there yet?
if (lf->cell == targetc) {
enum MOVEREASON mr;
// yes. remove target cell
if (db) dblog(".oO { arrived at f_targetcell. removing. }");
assert(f->id <= F_LAST);
mr = f->val[2];
if ((mr == MR_LF) && f->text) {
lifeform_t *targlf;
// if we were chasing someone, keep looking
// for them.
targlf = findlf(lf->cell->map, atoi(f->text));
if (targlf) {
if (db) dblog(".oO { resuming pursuit of %s }", targlf->race->name);
addflag(lf->flags, F_TARGETLF, targlf->id, NA, NA, NULL);
}
}
killflag(f);
} else {
if (!turned && (c != lf->cell)) {
turntoface(lf, c);
}
}
}
} else {
// !c
}
}
if (!c) {
if (db) dblog(".oO { f_targetcell doesn't exist anymore. moving randomly. }");
// destination doesn't exist!
loseaitargets(lf);
// move randomly now.
dorandommove(lf, B_NOBADMOVES, B_TRUE, B_FALSE); // this function will call rest() if we cant move
}
checkflagpile(lf->flags); // debug
checkflagpile_maplfs(lf->cell->map);
// success
return B_TRUE;
}
int aipickup(lifeform_t *lf, object_t *o) {
// special case
if ((o->type->id == OT_COFFIN) && (lf->race->id == R_GASCLOUD) && lfhasflagval(lf, F_ORIGRACE, R_VAMPIRE, NA, NA, NULL)) {
return rest(lf, B_TRUE);
}
// if (isedible(o)) {
if (caneat(lf, o)) {
return eat(lf, o);
} else {
return pickup(lf, o, o->amt, B_TRUE, B_TRUE);
}
return B_FALSE;
}
int aipickupok(lifeform_t *lf, object_t *o) {
int ok = B_FALSE;
// special case
if ((o->type->id == OT_COFFIN) && (lf->race->id == R_GASCLOUD) && lfhasflagval(lf, F_ORIGRACE, R_VAMPIRE, NA, NA, NULL)) {
return B_TRUE;
}
/*
if (hasflag(o->flags, F_SHOPITEM)) {
return B_FALSE;
}
*/
//if (isedible(o) && caneat(lf, o) && !isinbattle(lf, B_NODISTANT, B_FALSE)) {
if (isedible(o) && caneat(lf, o)) {
ok = B_TRUE;
} else if (canpickup(lf, o, 1)) {
ok = B_TRUE;
}
return ok;
}
int aiobok(lifeform_t *lf, object_t *o, lifeform_t *target) {
// non-humanoids can only use food.
if (!lfhasflag(lf, F_HUMANOID)) {
if (o->type->obclass->id != OC_FOOD) {
return B_FALSE;
}
}
switch (o->type->id) {
case OT_POT_INVIS:
case OT_WAND_INVIS:
if (target && lfhasflag(target, F_INVISIBLE)) {
return B_FALSE;
}
break;
case OT_POT_INVULN:
if (target && lfhasflag(target, F_INVULNERABLE)) {
return B_FALSE;
}
break;
default:
break;
}
return B_TRUE;
}
void aiturn(lifeform_t *lf) {
int db = B_FALSE;
int icanattack = B_FALSE;
flag_t *f;
lifeform_t *master = NULL;
enum ATTRBRACKET iqb;
checkflagpile_maplfs(lf->cell->map);
/*
if (wantdb && haslos(player, lf->cell)) {
db = B_TRUE;
} else {
db = B_FALSE;
}
*/
if (wantdb && lfhasflag(lf, F_DEBUG)) {
db = B_TRUE;
} else {
db = B_FALSE;
}
if (db) {
char lfname[BUFLEN];
real_getlfname(lf, lfname, NULL, B_SHOWALL, B_CURRACE);
dblog("AIMOVE: %s, facing %s", lfname, getdirnameshort(lf->facing));
}
// if lifeform isn't alive, skip turn
if (isdead(lf)) {
if (db) dblog(".oO { i am not alive, skipping turn. }");
taketime(lf, SPEED_DEAD);
checkflagpile_maplfs(lf->cell->map);
return;
}
// if game has just started and player hasn't had their turn yet,
// skip turn
if (!playerhasmoved) {
if (db) dblog(".oO { player hasn't had their initial turn yet, skipping turn. }");
taketime(lf, SPEED_DEAD);
checkflagpile_maplfs(lf->cell->map);
return;
}
///////////////////////////////////////////
// info gathering
///////////////////////////////////////////
// remember our intelligence
iqb = getattrbracket(getattr(lf, A_IQ), A_IQ, NULL);
// are we a pet?
f = lfhasflagval(lf, F_PETOF, NA, NA, NA, NULL);
if (f && (getallegiance(lf) == AL_FRIENDLY)) {
master = findlf(lf->cell->map, f->val[0]);
}
checkflagpile_maplfs(lf->cell->map);
ailoscheck(lf);
checkflagpile_maplfs(lf->cell->map);
///////////////////////////////////////////////
// emergencies / fixing up
///////////////////////////////////////////////
if (ai_handle_emergencies(lf, iqb)) return;
checkflagpile_maplfs(lf->cell->map);
ailoscheck(lf);
///////////////////////////////////////////////
// housekeeping - weapon changes, drop/pickup,
// use items, talk,etc
///////////////////////////////////////////////
if (ai_housekeeping(lf, master)) return;
ailoscheck(lf);
///////////////////////////////////////////////
// healing
///////////////////////////////////////////////
if (ai_healing(lf)) return;
checkflagpile_maplfs(lf->cell->map);
ailoscheck(lf);
///////////////////////////////////////////////
// inventory management
///////////////////////////////////////////////
if (ai_inventory_mgt(lf, &icanattack)) return;
checkflagpile_maplfs(lf->cell->map);
ailoscheck(lf);
///////////////////////////////////////////////
// attacking existing targets
///////////////////////////////////////////////
if (ai_attack_existing_target(lf)) return;
checkflagpile_maplfs(lf->cell->map);
ailoscheck(lf);
///////////////////////////////////////////////
// generic pre-movement actions.
///////////////////////////////////////////////
if (ai_premovement(lf)) return;
checkflagpile_maplfs(lf->cell->map);
ailoscheck(lf);
///////////////////////////////////////////////
// movement
///////////////////////////////////////////////
if (ai_movement(lf)) return;
checkflagpile_maplfs(lf->cell->map);
ailoscheck(lf);
///////////////////////////////////////////////
// look for something to do (objects, things
// to attack, etc)
///////////////////////////////////////////////
if (ai_bored(lf, master, icanattack)) return;
checkflagpile_maplfs(lf->cell->map);
ailoscheck(lf);
// DEFAULT - try to move in a random direction
if (db) dblog(".oO { default - moving randomly }");
checkflagpile_maplfs(lf->cell->map);
dorandommove(lf, B_NOBADMOVES, B_TRUE, B_FALSE); // this function will call rest() if we cant move
checkflagpile_maplfs(lf->cell->map);
ailoscheck(lf);
// somehow still here?
if (!lf->timespent) {
taketime(lf, getmovespeed(lf));
}
checkflagpile_maplfs(lf->cell->map);
}
// is the spell 'spellid' okay for AI lifeform 'lf' to cast at 'victim', for given purpose.
// purpose could be F_AICASTTOFLEE or F_ATCASTTOATTACK
int aispellok(lifeform_t *lf, enum OBTYPE spellid, lifeform_t *victim, enum FLAG purpose) {
objecttype_t *ot;
flag_t *f;
int db = B_FALSE;
int ok = B_FALSE;
int specialcase = B_FALSE;
int specificcheckok = B_FALSE;
int needlos = B_TRUE;
enum LOFTYPE needlof = LOF_NEED;
int castchance = 0;
enum SPELLTARGET targettype = TT_NONE;
if (lfhasflag(lf, F_DEBUG)) {
db = B_TRUE;
}
if (lfhasflag(lf, F_RAGE)) {
if (db) dblog(".oO { can't cast spells, i am enraged }");
return B_FALSE;
}
if (lfhasflag(lf, F_SILENCED)) {
if (db) dblog(".oO { can't cast spells, i am silenced }");
return B_FALSE;
}
ot = findot(spellid);
if (ot) {
flag_t *f;
f = hasflag(ot->flags, F_LOSLOF);
if (f) {
needlos = f->val[0];
needlof = f->val[1];
}
}
// override needlos/lof based on cast type
f = lfhasflag(lf, F_CASTTYPE);
if (f) {
switch (f->val[0]) {
case CT_GAZE:
needlos = B_TRUE;
break;
case CT_EYESPIT:
needlof = LOF_NEED;
break;
}
}
// enough mp etc?
if (!cancast(lf, spellid, NULL)) {
if (db) {
char why[BUFLEN];
if (reason == E_NOMP) {
strcpy(why, "not enough mp");
} else if (reason == E_CLIMBING) {
strcpy(why, "climbing");
} else if (reason == E_NOSTAM) {
strcpy(why, "not enough stamina");
} else if (reason == E_LOWIQ) {
strcpy(why, "lowiq");
} else if (reason == E_PRONE) {
strcpy(why, "prone");
} else if (reason == E_SWIMMING) {
strcpy(why, "swimming");
} else if (reason == E_TOOPOWERFUL) {
strcpy(why, "spell too powerful");
} else if (reason == E_NOTREADY) {
strcpy(why, "abil not ready");
} else if (reason == E_NEEDGRAB) {
strcpy(why, "needs grab");
} else if (reason == E_INJURED) {
strcpy(why, "injured");
} else {
strcpy(why, "unknown reason");
}
if (db) {
if (ot) {
dblog(".oO { can't cast %s right now (%s) (mpcost=%d, i have %d) }",
ot ? ot->name : "?unkownspell?", why,
getmpcost(lf, ot->id), lf->mp);
} else {
dblog(".oO { can't cast ?unknownspell? right now }");
}
}
}
return B_FALSE;
}
// boost spell already active?
if (hasflag(ot->flags, F_ONGOING)) {
if (lfhasflagval(lf, F_BOOSTSPELL, ot->id, NA, NA, NULL)) {
if (db) {
dblog(".oO { can't cast %s - it is already active.", ot ? ot->name : "?unkownspell?");
}
return B_FALSE;
}
}
f = lfhasflagval(lf, F_AISPELLTARGETOVERRIDE, ot->id, purpose, NA, NULL);
if (f) {
if (strlen(f->text)) {
castchance = atoi(f->text);
} else {
castchance = 100;
}
targettype = f->val[2];
} else {
f = hasflag(ot->flags, purpose);
if (f) {
if (f->val[1] == NA) {
castchance = 100;
} else {
castchance = f->val[1];
}
targettype = f->val[0];
}
}
if (f) {
if (pctchance(castchance)) {
int range, minrange,dist;
dist = getcelldist(lf->cell, victim->cell);
switch (targettype) {
case ST_VICTIM:
range = getspellrange(lf, spellid, getspellpower(lf, spellid), &minrange);
if (((range == UNLIMITED) || (dist <= range)) && (dist >= minrange)) {
if (db) {
dblog(".oO { spell possibility: %s }", ot ? ot->name : "?unkownspell?");
}
ok = B_TRUE;
}
break;
case ST_SELF:
case ST_ANYWHERE:
if (db) {
dblog(".oO { spell possibility: %s }", ot ? ot->name : "?unkownspell?");
}
ok = B_TRUE;
break;
case ST_ADJVICTIM:
if (dist == 1) {
if (ot->id == OT_A_GRAB) {
if (lfhasflag(lf, F_GRABBING) || lfhasflag(lf, F_GRABBEDBY) ||
lfhasflag(victim, F_GRABBING) || lfhasflag(victim, F_GRABBEDBY)) {
} else {
ok = B_TRUE;
}
} else if (ot->id == OT_A_CRUSH) {
// can only crush if you first grab something
if (lfhasflag(lf, F_GRABBING)) {
ok = B_TRUE;
}
} else if (ot->id == OT_A_SUCKBLOOD) {
// must attach first
if (lfhasflag(lf, F_ATTACHEDTO)) {
ok = B_TRUE;
}
} else {
ok = B_TRUE;
}
}
break;
case ST_ADJSELF:
if (dist == 1) {
ok = B_TRUE;
}
break;
case ST_SPECIAL:
specialcase = B_TRUE;
break;
case ST_NONE:
ok = B_FALSE;
break;
}
// now check for line of sight / fire
switch (targettype) {
case ST_VICTIM:
case ST_ADJVICTIM:
if (needlos && (!victim || !cansee(lf, victim)) ) {
if (db) dblog(".oO { cant cast %s - no LOS to victim }", ot ? ot->name : "?unkownspell?");
return B_FALSE;
}
if (needlof && !haslofknown(lf->cell, victim->cell, needlof, NULL) ) {
if (db) dblog(".oO { cant cast %s - no LOF to victim }", ot ? ot->name : "?unkownspell?");
return B_FALSE;
}
break;
default:
break;
}
} else { // failed pctchance for spell
if (db) dblog(".oO { failed pct check for casting %s }", ot ? ot->name : "?unkownspell?");
return B_FALSE;
}
} else {
// invalid spell for this purpose
if (db) dblog(".oO { cant cast %s - not valid for given purpose }", ot ? ot->name : "?unkownspell?");
return B_FALSE;
}
if (specialcase) {
if (ot->id == OT_A_CLIMB) {
int possible = B_TRUE,i;
enum ERROR reason;
// can't climb for a reason other than that there isn't
// a climbabnle cell in front of us?
if (!canclimb(lf, &reason)) {
if (reason != E_BADCLIMBDIR) {
possible = B_FALSE;
}
}
if (possible) {
// cell in sight and adjacent?
for (i = 1; i < lf->nlos; i++) {
if (!lf->los[i]->lf && lf->los[i]->type->solid && (getcelldist(lf->cell, lf->los[i]) == 1)) {
ok = B_TRUE;
break;
}
}
}
} else if (ot->id == OT_S_ANIMATESTATUE) {
cell_t *cell[MAXCANDIDATES];
int ncells,i;
// adjacent cell with a tree?
getradiuscells(lf->cell, 1, DT_COMPASS, B_TRUE, LOF_DONTNEED, B_FALSE, cell, &ncells, 0);
for (i = 0; i < ncells; i++) {
if (hasob(cell[i]->obpile, OT_STATUE)) {
ok = B_TRUE;
break;
}
}
} else if (ot->id == OT_S_ANIMATETREE) {
cell_t *cell[MAXCANDIDATES];
int ncells,i;
// adjacent cell with a tree?
getradiuscells(lf->cell, 1, DT_COMPASS, B_TRUE, LOF_DONTNEED, B_FALSE, cell, &ncells, 0);
for (i = 0; i < ncells; i++) {
if (hasob(cell[i]->obpile, OT_TREE)) {
ok = B_TRUE;
break;
}
}
} else if (ot->id == OT_S_DIG) {
cell_t *cell[MAXCANDIDATES];
int ncells,i;
getradiuscells(lf->cell, 1, DT_COMPASS, B_TRUE, LOF_DONTNEED, B_FALSE, cell, &ncells, 0);
for (i = 0; i < ncells; i++) {
if (cell[i]->type->solid &&
isdiggable(cell[i], OT_S_DIG) &&
getcelldist(cell[i], victim->cell) == 1) {
ok = B_TRUE;
break;
}
}
} else if (ot->id == OT_A_FULLSHIELD) {
object_t *targun,*sh;
int dist;
if (lfhasflag(lf, F_FULLSHIELD)) {
// turn off fullshield if:
// - i have no target
// - i have an adjacent target
if (!lfhasflag(lf, F_AIHITBYRANGED)) {
if (!victim || isinbattle(lf, B_FALSE, B_FALSE) ||
!isinbattle(lf, B_TRUE, B_FALSE)) {
ok = B_TRUE;
}
}
} else {
// turn on fullshield if:
//
// - I've recently been hit by a ranged attack.
// - I'm quite intelligent
// AND
// My target has a ranged weapon AND i'm not adjacent AND i'm in range.
sh = getshield(lf, DT_ALL);
if (sh) {
if (lfhasflag(lf, F_AIHITBYRANGED)) {
ok = B_TRUE;
} else {
enum ATTRBRACKET iqb;
iqb = getattrbracket(getattr(lf, A_IQ), A_IQ, NULL);
if (iqb >= AT_GTAVERAGE) {
targun = getfirearm(victim);
dist = getcelldist(lf->cell, victim->cell);
if (targun && getammo(targun) &&
haslof(victim->cell, lf->cell, LOF_NEED, NULL) &&
(dist > 1) && (dist <= getfirearmrange(targun)) ) {
ok = B_TRUE;
}
}
}
}
}
} else if (ot->id == OT_A_JUMP) {
cell_t *cell[MAXCANDIDATES],*c;
int ncells,i;
getradiuscells(lf->cell, 2, DT_COMPASS, B_TRUE, LOF_WALLSTOP, B_FALSE, cell, &ncells, 0);
for (i = 0; i < ncells; i++) {
c = cell[i];
if (!cellwalkable(lf, c, NULL) || celldangerous(lf, c, B_TRUE, NULL)) {
continue;
}
if (purpose == F_AICASTTOATTACK) {
// is this cell closer to the victim?
if (getcelldist(victim->cell, c) < getcelldist(victim->cell, lf->cell)) {
ok = B_TRUE;
break;
}
} else {
// is this cell further away from the victim?
if (getcelldist(victim->cell, c) > getcelldist(victim->cell, lf->cell)) {
ok = B_TRUE;
break;
}
}
}
} else if (ot->id == OT_S_PLANTWALK) {
cell_t *cell[MAXCANDIDATES];
int ncells,i;
getradiuscells(lf->cell, 1, DT_COMPASS, B_TRUE, LOF_DONTNEED, B_TRUE, cell, &ncells, 0);
// any plants within range 1?
for (i = 0; i < ncells; i++) {
if (hasobofclass(cell[i]->obpile, OC_FLORA)) {
ok = B_TRUE;
}
}
} else if (ot->id == OT_S_PSYSHOVE) {
if (purpose == F_AICASTTOATTACK) {
if (getlfweight(lf, B_WITHOBS) >= getlfweight(victim, B_WITHOBS)) {
ok = B_TRUE;
}
} else if (purpose == F_AICASTTOFLEE) {
if (getlfweight(lf, B_WITHOBS) < getlfweight(victim, B_WITHOBS)) {
ok = B_TRUE;
}
}
} else if (ot->id == OT_S_PYROMANIA) {
int i;
for (i = 0; i < lf->nlos; i++) {
if ((lf->los[i] != lf->cell) && getflamingobs(lf->los[i]->obpile, NULL, NULL)) {
ok = B_TRUE;
}
}
} else if (ot->id == OT_S_TELEKINESIS) {
int i,nposs;
float maxweight;
maxweight = getlfweight(lf, B_NOOBS) +
(getlfweight(lf, B_NOOBS) * (getstatmod(lf, A_IQ) / 100));
nposs = 0;
for (i = 0; i < lf->nlos; i++) {
if (lf->los[i] != lf->cell) {
object_t *o;
for (o = lf->los[i]->obpile->first ; o ; o = o->next) {
if (!hasflag(o->flags, F_NOPICKUP) &&
getobweight(o) <= maxweight) {
ok = B_TRUE;
break;
}
if (ok) break;
}
}
}
} else if (ot->id == OT_S_SUPERHEAT) {
// got a potion?
if (victim && hasobwithflag(lf->pack, F_DRINKABLE) &&
haslofknown(lf->cell, victim->cell, LOF_NEED, NULL)) {
ok = B_TRUE;
}
} else {
if (db) dblog(".oO { cant cast %s - specialcase conditions not yet coded }", ot ? ot->name : "?unkownspell?");
return B_FALSE;
}
}
if (!ok) {
if (db) dblog(".oO { cant cast %s - targetting conditions cannot be met }", ot ? ot->name : "?unkownspell?");
return B_FALSE;
}
// now check whether it meets specific spell conditions
specificcheckok = B_TRUE;
if (ot->id == OT_S_AIRBLAST) {
// target must be in a straight compass dir from us
if ((victim->cell->x != lf->cell->x) ||
(victim->cell->y != lf->cell->y) ||
(abs(victim->cell->x - lf->cell->x) != abs(victim->cell->y - lf->cell->y)) ) {
} else {
specificcheckok = B_FALSE;
}
}
if (ot->id == OT_S_ANIMATEDEAD) {
int found = B_FALSE,i;
// must be a corpse in sight
for (i = 0; i < lf->nlos; i++) {
if (hasob(lf->los[i]->obpile, OT_CORPSE)) {
found = B_TRUE;
break;
}
}
if (!found) {
specificcheckok = B_FALSE;
}
}
if (ot->id == OT_S_ANIMATEMETAL) {
object_t *wep;
wep = getweapon(lf);
if (!wep || !ismetal(wep->material->id)) {
specificcheckok = B_FALSE;
}
}
if ((ot->id == OT_S_BLINDNESS) && isblind(victim)) {
specificcheckok = B_FALSE;
}
if (ot->id == OT_S_BLINKASS) {
cell_t *targcell;
targcell = getcellindir(victim->cell, diropposite(victim->facing));
if (!cellwalkable(lf, targcell, NULL)) {
specificcheckok = B_FALSE;
}
}
if (ot->id == OT_S_BLOODBOIL) {
if (lfhasflag(victim, F_BLOODBOIL)) {
specificcheckok = B_FALSE;
}
}
if (ot->id == OT_S_DEATHKEEN) {
if (!isnighttime()) specificcheckok = B_FALSE;
}
if ((ot->id == OT_S_DANCINGFLAME) || (ot->id == OT_S_CLEANSINGFIRE)) {
int i;
int found = B_FALSE;
// any fire in sight?
for (i = 0; i < lf->nlos; i++) {
if (hasobofmaterial(lf->los[i]->obpile, MT_FIRE) ||
(hasobwithflag(lf->los[i]->obpile, F_ONFIRE))) {
found = B_TRUE;
break;
}
}
if (!found) {
specificcheckok = B_FALSE;
}
}
if (ot->id == OT_A_DISARM) {
if (purpose == F_AICASTTOFLEE) {
// check our own cell
if (!isdiggable(lf->cell, OT_S_DIG)) {
specificcheckok = B_FALSE;
}
}
}
if (ot->id == OT_A_DISARM) {
if (!getweapon(victim)) {
specificcheckok = B_FALSE;
}
}
if ((ot->id == OT_S_DISPERSAL) && (lf->race->raceclass->id == RC_GOD)) {
specificcheckok = B_FALSE;
}
if ((ot->id == OT_S_DISRUPTUNDEAD) && !isundead(victim)) {
specificcheckok = B_FALSE;
}
if ((ot->id == OT_S_DRAINLIFE) && isimmuneto(victim->flags, DT_NECROTIC, B_FALSE)) {
specificcheckok = B_FALSE;
}
if (ot->id == OT_S_GATHERFLAME) {
int i,found=B_FALSE;
// don't cast if we're made of fire!
if ((lf->material->id == MT_FIRE) && (getattrbracket(getattr(lf, A_WIS), A_WIS, NULL) >= AT_AVERAGE)) {
specificcheckok = B_FALSE;
} else {
// must be fire nearby
for (i = 0; i < lf->nlos; i++) {
if (lf->los[i]->lf && (lf->los[i]->lf->material->id == MT_FIRE)) {
found = B_TRUE;
break;
}
if (hasobofmaterial(lf->los[i]->obpile, MT_FIRE)) {
found = B_TRUE;
break;
}
}
if (!found) {
specificcheckok = B_FALSE;
}
}
}
if (ot->id == OT_A_FLIP) {
if (getlfsize(victim) > getlfsize(lf)) {
specificcheckok = B_FALSE;
}
}
if (ot->id == OT_A_FLURRY) {
if (!isdualweilding(lf)) {
specificcheckok = B_FALSE;
}
}
if ((ot->id == OT_S_FREEZEOB) && lfhasflag(lf, F_FREEZINGTOUCH)) {
specificcheckok = B_FALSE;
}
if ((ot->id == OT_S_HASTE) && (lfhasflag(victim, F_FASTACT) || lfhasflag(victim, F_FASTACTMOVE)) ) {
specificcheckok = B_FALSE;
}
if (ot->id == OT_S_HEAVENARM) {
if (lfhasflag(lf, F_HEAVENARM)) {
specificcheckok = B_FALSE;
}
}
if (ot->id == OT_A_HEAVYBLOW) {
object_t *w;
w = getweapon(lf);
if (!w || !isheavyweapon(w)) {
specificcheckok = B_FALSE;
}
}
if (ot->id == OT_A_HIDE) {
enum ERROR why;
if (lfhasflag(lf, F_HIDING)) {
specificcheckok = B_FALSE;
} else if (lfhasflag(lf, F_FEIGNINGDEATH)) {
specificcheckok = B_FALSE;
} else if (!safetorest(lf, &why) && (why != E_TOOSOON)) {
specificcheckok = B_FALSE;
} else if (lfproduceslight(lf, NULL)) {
specificcheckok = B_FALSE;
}
}
if (ot->id == OT_S_ICECRUST) {
object_t *wep;
wep = getweapon(lf);
if (!wep) {
specificcheckok = B_FALSE;
} else if (hasflag(wep->flags, F_FROZEN)) {
specificcheckok = B_FALSE;
}
}
if ((ot->id == OT_S_PAIN) && lfhasflag(victim, F_PAIN)) {
specificcheckok = B_FALSE;
}
if (ot->id == OT_S_PETRIFY) {
if (lfhasflag(victim, F_BEINGSTONED) || (victim->material->id == MT_STONE) ) {
specificcheckok = B_FALSE;
}
}
if ((ot->id == OT_S_HEALING) && (lf->hp >= lf->maxhp)) {
specificcheckok = B_FALSE;
}
if ((ot->id == OT_S_HEALINGMIN) && (lf->hp >= lf->maxhp)) {
specificcheckok = B_FALSE;
}
if ((ot->id == OT_S_INVISIBILITY) && lfhasflag(victim, F_INVISIBLE)) {
specificcheckok = B_FALSE;
}
if ((ot->id == OT_A_IRONFIST) && getweapon(lf)) {
specificcheckok = B_FALSE;
}
if (ot->id == OT_S_LETHARGY) {
if (getstamina(victim) <= 0) {
specificcheckok = B_FALSE;
}
}
if ((ot->id == OT_S_PARALYZE) && lfhasflag(victim, F_PARALYZED)) {
specificcheckok = B_FALSE;
}
if (ot->id == OT_S_PROPELMISSILE) {
if (!getbestthrowmissile(lf, victim)) {
specificcheckok = B_FALSE;
}
}
if (ot->id == OT_A_SHIELDBASH) {
if (!getshield(lf, DT_ALL)) {
specificcheckok = B_FALSE;
}
}
if ((ot->id == OT_S_SLEEP) && lfhasflag(victim, F_ASLEEP)) {
specificcheckok = B_FALSE;
}
if ((ot->id == OT_S_SLOW) && (lfhasflag(victim, F_SLOWACT) || lfhasflag(victim, F_SLOWACTMOVE)) ) {
specificcheckok = B_FALSE;
}
if ((ot->id == OT_S_SMITEGOOD) && (getalignment(victim) != AL_GOOD)) {
specificcheckok = B_FALSE;
}
if ((ot->id == OT_S_SMITEEVIL) && (getalignment(victim) != AL_EVIL)) {
specificcheckok = B_FALSE;
}
if ((ot->id == OT_A_SONICBOLT) && lfhasflag(lf, F_SILENCED)) {
specificcheckok = B_FALSE;
}
if (ot->id == OT_S_SPIKEVOLLEY) {
if ((lf->race->id == R_MANTICORE) && lfhasflagval(lf, F_INJURY, IJ_TAILBROKEN, NA, NA, NULL)) {
specificcheckok = B_FALSE;
}
}
if (ot->id == OT_A_SPRINT) {
if (lfhasflag(lf, F_SPRINTING) || !getstamina(lf) || (getstamina(lf) <= (getmaxstamina(lf)/2))) {
specificcheckok = B_FALSE;
}
if (isairborne(lf, NULL)) {
specificcheckok = B_FALSE;
}
}
if ((ot->id == OT_A_STEAL) || (ot->id == OT_S_CONFISCATE)) {
if (!countobs(victim->pack, B_FALSE)) {
specificcheckok = B_FALSE;
}
}
if (ot->id == OT_S_SUCK) {
if (getcelldist(lf->cell, victim->cell) <= 1) {
specificcheckok = B_FALSE;
}
}
if (ot->id == OT_S_SUMMONWEAPON) {
if (getweapon(lf)) {
specificcheckok = B_FALSE;
}
}
if ((ot->id == OT_A_SWOOP) || (ot->id == OT_A_CHARGE)) {
cell_t *adjcell;
flag_t *willflag, *srflag;
int srange = 5;
willflag = lfhasflagval(lf, F_CANWILL, ot->id, NA, NA, NULL);
if (willflag) {
texttospellopts(f->text, "range:", &srange, NULL);
if (!srange) srange = 5;
}
// override...
srflag = lfhasflag(lf, F_SWOOPRANGE);
if (srflag) {
srange = srflag->val[0];
}
adjcell = get_closest_adjcell(lf->cell, victim->cell);
if (!adjcell || celldangerous(lf, adjcell, B_TRUE, NULL)) { // don't charge into dangerous cells
specificcheckok = B_FALSE;
} else if (adjcell && adjcell->lf) {
specificcheckok = B_FALSE;
} else if (!haslofknown(lf->cell, victim->cell, LOF_NEED,NULL)) {
specificcheckok = B_FALSE;
} else if (isimmobile(lf)) {
specificcheckok = B_FALSE;
} else if ((ot->id == OT_A_SWOOP) && !lfhasflag(lf, F_FLYING)) {
specificcheckok = B_FALSE;
} else if ((ot->id == OT_A_SWOOP) && !lfhasflagval(lf, F_HASATTACK, OT_CLAWS, NA, NA, NULL)) {
specificcheckok = B_FALSE;
} else if (getcelldist(lf->cell, victim->cell) > srange) {
specificcheckok = B_FALSE;
} else if (getcelldist(lf->cell, victim->cell) < 2) { // ie too close
specificcheckok = B_FALSE;
}
}
if (ot->id == OT_A_TRIPLF) {
if (isairborne(victim, NULL)) {
specificcheckok = B_FALSE;
}
}
if ((ot->id == OT_A_TUMBLE) || (ot->id == OT_A_JUMP)) {
if (lfhasflag(lf, F_GRABBING) || lfhasflag(lf, F_GRABBEDBY) || isairborne(lf, NULL)) {
specificcheckok = B_FALSE;
}
}
if ((ot->id == OT_A_WARCRY) && lfhasflag(lf, F_SILENCED)) {
specificcheckok = B_FALSE;
}
if (ot->id == OT_S_WARPWOOD) {
specificcheckok = B_FALSE;
if (victim) {
object_t *oo;
for (oo = victim->pack->first ; oo ; oo = oo->next) {
if ((oo->type->material->id == MT_WOOD) && isequipped(oo)) {
specificcheckok = B_TRUE;
break;
}
}
}
}
if (ot->id == OT_S_WEAKEN) {
flag_t *lff;
for (lff = lf->flags->first; lff ; lff = lff->next) {
if ((lff->id == F_ATTRMOD) && (lff->val[0] == A_STR) && (lff->obfrom == OT_S_WEAKEN)) {
specificcheckok = B_FALSE;
break;
}
}
}
if (!specificcheckok) {
if (db) dblog(".oO { cant cast %s - specific spell check failed }", ot ? ot->name : "?unkownspell?");
return B_FALSE;
}
return B_TRUE;
}
int aiwants(lifeform_t *lf, object_t *o, int *covets) {
enum OBTYPE oid[MAXPILEOBS];
int oidcovet[MAXPILEOBS];
int noids = 0;
enum FLAG wantflag[MAXPILEOBS];
int wantflagcovet[MAXPILEOBS];
int nwantflags = 0;
makewantedoblist(lf, &noids, oid, oidcovet, &nwantflags, wantflag, wantflagcovet);
return aiwants_real(lf, o, covets, &noids, oid, oidcovet, &nwantflags, wantflag, wantflagcovet);
}
int aiwants_real(lifeform_t *lf, object_t *o, int *covets, int *noids, enum OBTYPE *oid, int *oidcovet,int *nwantflags, enum FLAG *wantflag, int *wantflagcovet) {
int i;
if (hasflagval(o->flags, F_HOMEOBFOR, lf->id, NA, NA, NULL)) {
return B_FALSE;
}
for (i = 0; i < *noids; i++) {
if (oid[i] == o->type->id) {
if (covets) {
*covets = oidcovet[i];
}
return B_TRUE;
}
}
for (i = 0; i < *nwantflags; i++) {
// special case to cope with eating objects that aren't normally edible.
// eg. goats eating wood due to F_CANEATMATERIAL
if ((wantflag[i] == F_EDIBLE) && caneat(lf, o)) {
if (covets) *covets = wantflagcovet[i];
return B_TRUE;
}
if (hasflag(o->flags, wantflag[i])) {
if ((wantflag[i] == F_EDIBLE) && !caneat(lf, o)) { // special case
} else {
if (covets) {
*covets = wantflagcovet[i];
}
return B_TRUE;
}
}
}
return B_FALSE;
}
lifeform_t *gettargetlf(lifeform_t *lf) {
flag_t *f;
lifeform_t *target = NULL;
f = hasflag(lf->flags, F_TARGETLF);
if (f) {
target = findlf(lf->cell->map, f->val[0]);
}
return target;
}
object_t *hasbetterarmour(lifeform_t *lf, obpile_t *op) {
object_t *o;
for (o = op->first ; o ; o = o->next) {
if (isarmour(o)) {
object_t *curarm;
enum BODYPART bp;
flag_t *f;
// where does it go?
f = hasflag(o->flags, F_GOESON);
bp = f->val[0];
// is it better than what we have in that position?
curarm = getarmour(lf, bp);
if (isbetterwepthan(o, curarm, lf)) {
return o;
}
}
}
return NULL;
}
object_t *hasbetterweapon(lifeform_t *lf, obpile_t *op) {
object_t *bestwep, *o;
bestwep = getbestweapon(lf);
for (o = op->first ; o ; o = o->next) {
if (isweapon(o) && isbetterwepthan(o, bestwep, lf) && canweild(lf, o)) {
return o;
}
}
return NULL;
}
int isvalidattacktarget(lifeform_t *lf, lifeform_t *victim) {
flag_t *f;
// lifeforms won't attack monster summoners
f = hasflagval(lf->flags, F_SUMMONEDBY, victim->id, NA, NA,NULL);
if (f) {
if (!isplayer(victim)) {
return B_FALSE;
}
}
// pets won't (intentionally) attack their masters
f = hasflagval(lf->flags, F_PETOF, victim->id, NA, NA,NULL);
if (f) {
return B_FALSE;
}
return B_TRUE;
}
// returns B_TRUE if we did something
int lookforobs(lifeform_t *lf) {
object_t *o,*nexto;
enum OBTYPE oid[MAXPILEOBS];
int oidcovet[MAXPILEOBS];
int noids = 0;
enum FLAG wantflag[MAXPILEOBS];
int wantflagcovet[MAXPILEOBS];
int nwantflags = 0;
flag_t *f;
cell_t *c;
int i;
int db = B_FALSE;
lifeform_t *target;
int targdist = 999;
int covets = B_FALSE;
target = gettargetlf(lf);
if (target) {
targdist = getcelldist(lf->cell, target->cell);
}
if (wantdb && lfhasflag(lf, F_DEBUG)) {
db = B_TRUE;
} else {
db = B_FALSE;
}
makewantedoblist(lf, &noids, oid, oidcovet, &nwantflags, wantflag, wantflagcovet);
// current cell has an object we want?
for (o = lf->cell->obpile->first ; o ; o = nexto) {
nexto = o->next;
if (aiwants_real(lf, o, &covets, &noids, oid, oidcovet, &nwantflags, wantflag, wantflagcovet)) {
int getit = B_TRUE;
// if we are in battle only go for it if we covet it
if (target && !covets) getit = B_FALSE;
if (isdangerousob(o, lf, B_TRUE) || !aipickupok(lf, o)) getit = B_FALSE;
if (getit) {
if (db) dblog(".oO { current cell has ob i want (%s) }",o->type->name);
// try to pick it up
if (!aipickup(lf, o)) return B_TRUE;
if (db) dblog(".oO { pickup of %s failed, trying to eat! }",o->type->name);
if (!eat(lf, o)) return B_TRUE;
if (db) dblog(".oO { eating %s failed }",o->type->name);
}
}
}
// current cell has better weapon?
if (lfhasflag(lf, F_HUMANOID) && hasbp(lf, BP_WEAPON)) {
f = hasflag(lf->flags, F_WANTSBETTERWEP);
if (f) {
// if we are in battle only go for it if we covet it
if (!target || (f->val[1] == B_COVETS)) {
o = hasbetterweapon(lf, lf->cell->obpile);
if (o && !isdangerousob(o, lf, B_TRUE) && aipickupok(lf, o) && canpickup(lf, o, 1)) {
if (db) dblog(".oO { current cell has better weapon (%s) }",o->type->name);
// try to pick it up
if (!aipickup(lf, o)) return B_TRUE;
if (db) dblog(".oO { pickup of better wep %s failed! }",o->type->name);
}
}
}
}
if (lfhasflag(lf, F_HUMANOID)) {
// current cell has better armour?
f = hasflag(lf->flags, F_WANTSBETTERARM);
if (f ) {
// if we are in battle only go for it if we covet it
if (!target || (f->val[1] == B_COVETS)) {
o = hasbetterarmour(lf, lf->cell->obpile);
if (o && !isdangerousob(o, lf, B_TRUE) && aipickupok(lf, o) && canpickup(lf, o, 1)) {
if (db) dblog(".oO { current cell has better armour (%s) }",o->type->name);
// try to pick it up
if (!aipickup(lf, o)) return B_TRUE;
if (db) dblog(".oO { pickup of better armour %s failed! }",o->type->name);
}
}
}
}
// look around for objects which we want, if we don't already have a targetcell.
// CRASH in here!
if (!hasflag(lf->flags, F_TARGETCELL)) {
if (db) dblog(".oO { no targetcell, so looking for remote objects }");
if (lf->losdirty) precalclos(lf);
lf->loslock = B_TRUE;
for (i = 0 ; i < lf->nlos; i++) {
int gothere = B_FALSE;
int celldist;
c = lf->los[i];
celldist = getcelldist(lf->cell, c);
if ((c != lf->cell) && (!c->lf || cancast(lf, OT_A_SNATCH, NULL)) &&
!lfhasflagval(lf, F_IGNORECELL, c->x, c->y, NA, NULL)) {
for (o = c->obpile->first ; o ; o = nexto) {
nexto = o->next;
if (aiwants_real(lf, o, &covets, &noids, oid, oidcovet, &nwantflags, wantflag, wantflagcovet) &&
aipickupok(lf, o)) {
gothere = B_TRUE;
// if we are in battle only go for it if we covet it and
// it's similar distance to our target
if (target) {
if (!covets || (celldist > targdist)) {
gothere = B_FALSE;
}
}
if (gothere) {
if (db) dblog(".oO { remote cell has ob i want (%s). setting f_targetcell. }",o->type->name);
break;
}
} // end if aiwantsthisob
} // end foreach ob in cell
if (!gothere) {
if (lfhasflag(lf, F_HUMANOID) && hasbp(lf, BP_WEAPON)) {
// remote cell has better weapon?
f = hasflag(lf->flags, F_WANTSBETTERWEP);
if (f) {
// if we are in battle only go for it if we covet it
if (!target ||
((f->val[1] != B_COVETS) && (celldist <= targdist)) ) {
o = hasbetterweapon(lf, c->obpile);
if (o && !isdangerousob(o, lf, B_TRUE) && aipickupok(lf, o)) {
if (db) dblog(".oO { remote cell has better weapon (%s). setting f_targetcell }",o->type->name);
gothere = B_TRUE;
}
}
}
}
}
if (!gothere) {
if (lfhasflag(lf, F_HUMANOID)) {
// remote cell has better armour?
f = hasflag(lf->flags, F_WANTSBETTERARM);
if (f) {
if (!target ||
((f->val[1] != B_COVETS) && (celldist <= targdist)) ) {
o = hasbetterarmour(lf, c->obpile);
if (o && !isdangerousob(o, lf, B_TRUE) && aipickupok(lf, o)) {
if (db) dblog(".oO { remote cell has better armour (%s). setting f_targetcell }",o->type->name);
gothere = B_TRUE;
}
}
}
}
}
if (gothere) {
long obid;
int success = B_FALSE;
// remember object id.
obid = o->id;
// cast a spell to get it?
if (cancast(lf, OT_S_CALLWIND, NULL) && haslofknown(lf->cell, c, LOF_NEED, NULL)) {
if (!castspell(lf, OT_S_CALLWIND, NULL, o, c, NULL, NULL)) {
success = B_TRUE;
}
}
if (cancast(lf, OT_A_SNATCH, NULL) && haslofknown(lf->cell, c, LOF_NEED, NULL) &&
(getcelldist(lf->cell, c) == 1)) {
// only snatch those things which we can carry
if (canpickup(lf, o, 1)) {
if (!useability(lf, OT_A_SNATCH, NULL, c)) {
success = B_TRUE;
}
}
}
if (success) {
// got the object. now try to eat it if possible.
object_t *oo;
oo = hasobid(lf->pack, obid);
if (oo && isedible(oo) && caneat(lf, oo)) {
eat(lf, o);
}
return B_TRUE;
}
// start walking towards target cell
if (aigoto(lf, c, MR_OB, o, aigetchasetime(lf))) {
return B_FALSE;
}
}
} else if ((celldist == 1) && c->lf &&
(isunconscious(c->lf) || isasleep(c->lf)) &&
(getattrbracket(getattr(lf, A_IQ), A_IQ, NULL) > IQ_ANIMAL) && !isundead(lf) && !willeatlf(lf, c->lf)) {
// intelligent enemies will loot unconscious/sleeping lfs to make sure they are not a threat.
//
// in this case they'll loot more than normal. even if they wouldn't normally pick up
// some of these objects, they'll assume they are good because the player was holding
// them.
int getit = B_FALSE;
for (o = c->lf->pack->first ; o ; o = nexto) {
nexto = o->next;
getit = B_FALSE;
if (aiwants_real(lf, o, &covets, &noids, oid, oidcovet, &nwantflags, wantflag, wantflagcovet)) {
getit = B_TRUE;
} else if (areenemies(lf, c->lf) && (isweapon(o) || isarmour(o))) {
getit = B_TRUE;
} else {
if (aiobok(lf, o, NULL)) {
flag_t *retflag[MAXCANDIDATES];
int nretflags;
getflags(o->flags, retflag, &nretflags, F_AIBOOSTITEM, F_AIFLEEITEM, F_AIHEALITEM, F_NONE);
if (nretflags) {
getit = B_TRUE;
}
}
}
// if we are in battle with someone OTHER than this lf, then only go for it if we covet it
if (getit) {
if (target && (target != c->lf) && !covets) getit = B_FALSE;
if (isdangerousob(o, lf, B_TRUE) || !aipickupok(lf, o)) getit = B_FALSE;
}
if (getit) {
break;
}
}
if (getit) {
int returnnow = B_FALSE;
if (db) dblog(".oO { adjacent unconscious lf has ob i want (%s) }",o->type->name);
// try to pick it up
if (aipickup(lf, o)) {
if (db) dblog(".oO { pickup of %s failed, trying to eat! }",o->type->name);
} else {
returnnow = B_TRUE; // got it!
}
if (!returnnow) {
if (eat(lf, o)) {
if (db) dblog(".oO { eating %s failed }",o->type->name);
} else {
returnnow = B_TRUE;
}
}
if (returnnow) {
// if they were only sleeping, they wake up a bit
f = isasleep(c->lf);
if (f) {
timeeffectsflag(f, 3 + rnd(1,3));
}
return B_TRUE;
}
}
} // end if looting sleeping lfs
} // end foreach los cell
lf->loslock = B_FALSE;
}
if (db) dblog(".oO { didn't find any obs i want }");
return B_FALSE;
}
int loseaitargets(lifeform_t *lf) {
int donesomething = B_FALSE;
if (killflagsofid(lf->flags, F_AIPATH)) donesomething = B_TRUE;
if (killflagsofid(lf->flags, F_TARGETLF)) donesomething = B_TRUE;
if (killflagsofid(lf->flags, F_TARGETCELL)) donesomething = B_TRUE;
return donesomething;
}
void makewantedoblist(lifeform_t *lf, int *noids, enum OBTYPE *oid, int *oidcovet,int *nwantflags, enum FLAG *wantflag, int *wantflagcovet) {
int i;
flag_t *f;
flag_t *retflag[MAXCANDIDATES];
int nretflags = 0;
// construct a list of objects which we want
*noids = 0;
getflags(lf->flags, retflag, &nretflags, F_WANTS, F_WANTSOBFLAG, F_NONE);
for (i = 0; i < nretflags; i++) {
f = retflag[i];
if (f->id == F_WANTS) {
oid[*noids] = f->val[0];
oidcovet[*noids] = (f->val[1] == B_COVETS) ? B_TRUE : B_FALSE;
(*noids)++;
} else if (f->id == F_WANTSOBFLAG) {
wantflag[*nwantflags] = f->val[0];
wantflagcovet[*nwantflags] = (f->val[1] == B_COVETS) ? B_TRUE : B_FALSE;
(*nwantflags)++;
}
if (*nwantflags >= MAXCANDIDATES) {
raise(SIGINT);
}
}
if (hasflag(lf->flags, F_HUNGER)) {
// monsters dont normaly need to eat. if they have hunger, it's due
// to a spell.
wantflag[*nwantflags] = F_EDIBLE;
wantflagcovet[*nwantflags] = B_TRUE;
(*nwantflags)++;
if (*nwantflags >= MAXCANDIDATES) {
raise(SIGINT);
}
}
}
// try to use an item with the given flag on ourself.
// returns B_FALSE if successful
int useitemwithflag(lifeform_t *lf, enum FLAG whichflag) {
object_t *o;
// can't use anything if enraged
if (lfhasflag(lf, F_RAGE)) return B_TRUE;
// aicontrolled human won't use items
if (lfhasflag(lf, F_AICONTROLLED)) return B_TRUE;
// only humanoids can use items
if (!lfhasflag(lf, F_HUMANOID)) return B_TRUE;
for (o = lf->pack->first ; o ; o = o->next) {
if (hasflag(o->flags, whichflag)) {
if (aiobok(lf, o, lf)) {
if (o->type->obclass->id == OC_POTION) {
if (candrink(lf, o)) {
quaff(lf, o);
return B_FALSE;
}
} else if ((o->type->obclass->id == OC_SCROLL) && !lfhasflag(lf, F_SILENCED)) {
if (!readsomething(lf, o)) {
return B_FALSE;
}
} else if ((o->type->obclass->id == OC_WAND) && getcharges(o) && canoperate(lf, o, NULL)) {
// if wand, use it on ourself
if (!operate(lf, o, lf->cell)) {
return B_FALSE;
}
// here on are special cases
} else if (o->type->id == OT_ASHCONCEAL) {
throwat(lf, o, lf->cell);
}
}
}
}
// failed to use an item
return B_TRUE;
}