#include #include #include #include #include #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. 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) { if (((int)c->known < (int)PR_INEPT) || ((int)c->known > (int)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 (c && (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) && getobmass(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 = NULL; 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) { if (!gotosleep(lf, B_NOWAKEWHENHEALED)) { 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_WAKEWHENHEALED : B_NOWAKEWHENHEALED)) { 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 if (db) { dblog(".oO { 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 = getobmass(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)) { 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; // ooo is f bad here? // 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 if (f->val[2] == MR_OTHER) { // 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) && getobmass(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; } // f is the F_TARGETCELL flag we're using int aimovetotargetcell(lifeform_t *lf, flag_t *f) { int x,y; cell_t *c = NULL,*origc,*targetc; int db = B_FALSE; enum ATTRBRACKET iqb; assert(f->id <= F_LAST); 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) { int failed = B_FALSE; // try to move towards the cell if (movetowards(lf, c, DT_ORTH, B_FALSE )) { failed = B_TRUE; } else { // check that our movement didn't make us lose our target cell // in a less obvious way eg. unopenable door in the way. cell_t *newc; flag_t *newflag; newc = aigettargetcell(lf, &newflag); if (!newc || !newflag || (newc != c) || (newflag != f)) { failed = B_TRUE; } } if (failed) { // 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 { // move successful 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); } assert(f->id <= F_LAST); 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; float maxweight; maxweight = getlfweight(lf, B_NOOBS) + (getlfweight(lf, B_NOOBS) * (getstatmod(lf, A_IQ) / 100)); 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) && getobmass(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; }