/* * Monster common code and support. */ #include #include #include "obj.h" #include "vars.h" #include "dice.h" #include "util.h" #include "fight.h" #include "pline.h" #include "fuses.h" #include "display.h" #include "structs.h" #include "montypes.h" #include "mon-@-you.h" #include "mon.h" /* * seestairmon is the monster the player is aware of at the other end * of stairs, or nil if there is no such. */ static MONST *seestairmon = 0; static void mon_settick(MONST *); /* forward */ /* * The fuse function driving each monster's actions. See also * mon_settick(). */ static void mon_tick(long int arg_li __attribute__((__unused__)), void *mvp) { MONST *m; m = mvp; if (m->flags & MF_DEAD) panic("ticking dead monster"); m->age ++; (*m->ops->tick)(m); mon_settick(m); } /* * Create a fuse for the monster's next action. */ static void mon_settick(MONST *m) { m->fuseid = addfuse_rel(&mon_tick,0,m,m->speed); } /* * Create a new monster. type is the monster type; dlevel is the * dungeon level it should be created on. This handles calling the * new monster's type's new routine. */ MONST *newmon(int type, LEVEL *dlevel) { MONST *m; m = malloc(sizeof(MONST)); m->type = &montypes[type]; m->symbol = '\\'; m->hp = 0; m->maxhp = 0; m->heal = 0; m->flags = 0; m->speed = 1 * TIMESCALE; m->age = 0; m->created = curtime; m->fuseid = 0; m->you.x = 0; m->you.y = 0; m->private = 0; m->link = 0; m->ops = 0; inv_init(&m->invent,IT_MON,m); m->lastloc = 0; m->loc = 0; m = (*montypes[type].info->new)(m,dlevel); if (m) mon_settick(m); return(m); } /* * Place a monster, m, at a location, lc. The monster is assumed to be * new, or at least not on the all-monsters chain. */ int placemon(MONST *m, LOC *lc) { if (lc->monst) { pline("placemon to occupied location?"); impossible(); (*m->ops->destroy)(m); return(1); } else { m->loc = lc; lc->monst = m; lc->on->nmonst ++; (*m->ops->bemoved)(m); upddisp1(lc); m->link = mchain; mchain = m; mchain_count ++; return(0); } } /* * Make a new monster. The level to create it on is random, but with a * heavy bias in favour of the level the player is on. If Iuz is mad, * monsters everywhere are created as if on the bottom dungeon level. */ static void makemon(void) { int probs[N_LEVELS]; int probv[MON__N]; int tprob; int i; LEVEL *lv; MONST *m; int dlevel; int mlevel; tprob = 0; for (i=0;incave / 10) - lv->nmonst; if (probs[i] < 0) probs[i] = 0; tprob += probs[i]; } i = you->loc->on->index + LEVEL_OFFSET; tprob -= probs[i]; probs[i] *= 10; tprob += probs[i]; if (tprob < 1) { pline("makemon: level total prob (%d) < 1",tprob); return; } tprob = rnd(tprob); for (i=0;i= N_LEVELS) { panic("can't find level for makemon (tprob = %d)",tprob); } dlevel = i; if (Z_mad) { mlevel = DUNGEON_LEVELS - 1; } else { mlevel = i; } lv = &Levels[dlevel]; tprob = 0; for (i=0;iprobs[mlevel]; tprob += probv[i]; } if (tprob < 1) { return; } tprob = rnd(tprob); for (i=0;i= MON__N) { panic("can't find monster in makemon (tprob = %d)",tprob); } m = newmon(i,lv); if (placemon(m,outofsight(lv))) { pline("makemon: can't place monster"); return; } } /* * The fuse function for creating monsters. The reason arg_li is * tested before recreating the fuse is that this is also used to * create monsters on demand, and in such a case it should not * re-addfuse itself. */ void makemon_fuse(long int arg_li, void *arg_vp __attribute__((__unused__))) { if (! arg_li) addfuse_rel(&makemon_fuse,0,0,roll("2d10+15")*TIMESCALE); makemon(); } /* * Check to see whether the player needs to be told about a monster at * the other end of stairs. This returns 0 if there's no monster * there or if the player has already been told about it, 1 otherwise. */ int seestairs(void) { LOC *lc; lc = you->loc->to; if ( !lc->monst || (lc->monst == seestairmon) ) { return(0); } pline("There's %s at the other end of the stairs!",amonname(lc->monst)); seestairmon = lc->monst; return(1); } /* * Generate a noun phrase naming the monster, for use in things like * "There's %s there!". * * This depends on all monster names being such that testing whether * the first character is a vowel is sufficient for a-vs-an decisions. * (If we had a "unicorn", for example, this would not be so.) */ char *amonname(MONST *m) { static char buf[64]; const char *cp; cp = m->type->name; sprintf(buf,"%s %s",vowel(cp[0])?"an":"a",cp); return(buf); } /* * The kill method for most mosnters: destroy its action fuse, drop its * inventory, clear it out of its location, update that location, and * mark it dead (and thus to be finally destroyed next time through * tick_monsters). */ void std_kill(MONST *m) { cancelfuse(m->fuseid); mergeinv(&m->invent,&m->loc->objs); m->loc->monst = 0; m->loc->on->nmonst --; upddisp(m->loc); m->flags |= MF_DEAD; } /* * The destroy method for most monsters. */ void std_destroy(MONST *m) { free(m); } /* * Kill a monster. */ void killmon(MONST *m) { (*m->ops->kill)(m); } /* * Move a monster to a location. */ void movemon(MONST *m, LOC *to) { m->loc->monst = 0; m->loc->on->nmonst --; upddisp1(m->loc); to->monst = m; m->loc = to; to->on->nmonst ++; (*m->ops->bemoved)(m); upddisp1(to); } /* * Cause a monster which is standing on stairs to take them. Returns 1 * if it did so, 0 if it couldn't for some reason (eg, the stairs lead * out of the dungeon, or there was a monster on the other end of the * stairs). */ static int takestairs(MONST *m) { LOC *lc; LOC *lc2; lc = m->loc; if ( (lc->type != LOC_CAVE) || ( (lc->cavetype != LOC_STAIRS_U) && (lc->cavetype != LOC_STAIRS_D) ) ) { panic("non-stairs in takestairs?"); } lc2 = lc->to; if (lc2 == 0) { return(0); } if (lc2->monst == 0) { movemon(m,lc2); return(1); } if ((m == you) || (lc2->monst == you)) { if (seestairs()) { return(1); } mhitm(m,lc2->monst); return(1); } return(0); } /* * Try to move a monster next to a location. This may actually put it * further away if everywhere close is occupied. This is used, for * example, when a monster walks into a gate, to place the monster at * the paired gate. */ static void monnextto(MONST *m, LOC *to) { int bestd; int bestn; int tx; int ty; int x; int y; int dx; int dy; LOC *gotlc; LEVEL *lv; LOC *lc; lv = to->on; tx = to->x; ty = to->y; bestd = (LEV_X+LEV_Y+1) * (LEV_X+LEV_Y+1); bestn = 0; gotlc = 0; for (x=0;x tx) ? x - tx : tx - x; if ((dx >= LEV_X/2) && (lv->flags & LVF_WRAPAROUND)) { dx = LEV_X - dx; } dx *= dx; lc = &lv->cells[x][0]; for (y=0;y ty) ? y - ty : ty - y; if ((dy >= LEV_Y/2) && (lv->flags & LVF_WRAPAROUND)) { dy = LEV_Y - dy; } if ( (lc->type == LOC_CAVE) && (lc->monst == 0) ) { int df; df = dx + (dy * dy); if ((df > 0) && (df < bestd)) { bestd = df; bestn = 1; } else if (df == bestd) { bestn ++; } if ((df == bestd) && onein(bestn)) { gotlc = lc; } } lc ++; } } if (bestn == 0) { panic("No free space on level for monnextto?"); } if (! gotlc) { panic("monnextto: have cell but can't find it?"); } movemon(m,gotlc); } /* * A monster (m) has just walked into a gate; place it next to the * paired gate's location (to). */ void gatemon(MONST *m, LOC *to) { monnextto(m,to); } /* * Toggle aggravation. Developer support. */ void aggravate(void) { MONST *m; Z_mad = ! Z_mad; pline("Aggravate %s",Z_mad?"on":"off"); for (m=mchain;m;m=m->link) { (*m->type->info->monctl)(m,MCTL_AGGR,1); } } /* * Cause a monster to follow a track. tracktype is the kind of track */ static int followtrack(MONST *m, int tracktype) { LOC *b; LOC *lc; if ((tracktype < 0) || (tracktype >= NTRACKTYPES)) panic("followtrack: bad tracktype %d",tracktype); lc = m->loc; b = lc->tracks[tracktype]; if (b) { if (b->monst == you) { mhitm(m,you); } else if (b->monst) { if (b->on == lc->on) { int x1; int x2; int y1; int y2; int x; int y; LOC *lc2; int n; x = b->x; y = b->y; x1 = x + ((x > 0) ? -1 : 0); x2 = x + ((x >= LEV_X) ? 0 : 1); y1 = y + ((y > 0) ? -1 : 0); y2 = y + ((y >= LEV_Y) ? 0 : 1); n = 0; for (x=x1;x<=x2;x++) { for (y=y1;y<=y2;y++) { lc2 = &b->on->cells[x][y]; if ( !lc2->monst && lc2->tracks[tracktype] && ( (lc2->type == LOC_CAVE) || ( (lc2->type == LOC_DOOR) && (lc2->walldoor & LOC_DOOR_OPEN) ) ) && ( onein(5) || (lc2->tracks[tracktype] == b->tracks[tracktype]) ) ) { n ++; if (onein(n)) b = lc2; } } } } } if (! b->monst) { if (b->type == LOC_GATE) { gatemon(m,b->to); } else if ( (b->type == LOC_DOOR) && !(b->walldoor & LOC_DOOR_OPEN) ) { if (tracktype == TRACK_SMART) { opendoor(b); } } else { movemon(m,b); if ((b->flags & LF_VISIBLE) && (b->type == LOC_SDOOR)) { finddoor(b); opendoor(b); resee(); } } } return(1); } else { return(0); } } /* * Cause a monster (m) to head for a location (tox,toy). If smart is * true, try to do so more intelligently. */ static int headfor(MONST *m, int tox, int toy, int smart) { LOC *ml; LOC *target; int i; int j; int k; LOC *lc; int d2[9]; int di[9]; LOC *l[9]; int n; int bestx; int mi; ml = m->loc; if ((tox == ml->x) && (toy == ml->y)) return(0); target = &ml->on->cells[tox][toy]; mi = distancei(ml,target); if (mi < 2) { if (target->monst == you) { mhitm(m,you); } else if (target->monst) { return(0); } else { movemon(m,target); } return(1); } n = 0; bestx = -1; for (i=1;i>=-1;i--) { for (j=1;j>=-1;j--) { lc = movecell(ml,i,j,0); if ( (lc->type == LOC_CAVE) || ( (lc->type == LOC_DOOR) && (lc->walldoor && LOC_DOOR_OPEN) ) ) { if (lc->monst) continue; l[n] = lc; d2[n] = distance2(lc,target); di[n] = distancei(lc,target); if ((bestx < 0) || (di[n] < di[bestx])) bestx = n; n ++; } } } if (bestx < 0) return(0); if (smart) { if (di[bestx] == 1) { if (onein(2)) return(1); j = 0; for (i=n-1;i>=0;i--) { if (di[i] == 1) { j ++; if (onein(j)) bestx = i; } } if (j < 1) abort(); movemon(m,l[bestx]); return(1); } bestx = -1; for (i=n-1;i>=0;i--) { if ((bestx < 0) || (d2[i] < d2[bestx])) bestx = i; } if (di[bestx] > mi) return(0); if (d2[bestx] == (di[bestx]*di[bestx])) { j = 0; for (i=n-1;i>=0;i--) { if ((di[i] == di[bestx]) && (d2[i] != d2[bestx])) { if ((j > 0) && (d2[i] < d2[k])) j = 0; j ++; if (onein(j)) k = i; } } if (j > 0) { movemon(m,l[k]); return(1); } } movemon(m,l[bestx]); return(1); } else { bestx = -1; for (i=n-1;i>=0;i--) { if (di[i] > mi) continue; if ((bestx < 0) || (d2[i] < d2[bestx])) bestx = i; } if (bestx < 0) return(0); movemon(m,l[bestx]); return(1); } #if 0 bestd = distance2(ml,target); if ((bestd < 4) && target->monst) { if (target->monst == you) { mhitm(m,you); } return(0); } bestl = ml; idx = rnd(8); for (i=0;i<8;i++,idx=(idx+1)&7) { lc = movecell(ml,delta[idx][0],delta[idx][1],0); if ( (lc->type == LOC_CAVE) || ( (lc->type == LOC_DOOR) && (lc->walldoor & LOC_DOOR_OPEN) ) ) { if (! lc->monst) { int df; df = distance2(lc,target); if ((df < 4) && smart) if (df < bestd) { bestd = df; bestl = lc; } } } } if (bestl != ml) { movemon(m,bestl); } return(1); #endif } /* * The standard animal-intelligence monster tick handler. */ void std_animal_tick(MONST *m) { LOC *lc; int x; int y; LOC *to; m->age ++; gainhp(m,m->heal); lc = m->loc; if (lc->flags & LF_VISIBLE) { m->flags |= MF_SAWYOU; m->you.x = you->loc->x; m->you.y = you->loc->y; } if (m->age > 1000) { m->flags |= MF_TRACKING; } if (m->flags & MF_SAWYOU) { if (headfor(m,m->you.x,m->you.y,0)) return; if ((lc->cavetype == LOC_STAIRS_U) || (lc->cavetype == LOC_STAIRS_D)) { takestairs(m); } m->flags &= ~MF_SAWYOU; } else if (m->flags & MF_TRACKING) { if (followtrack(m,TRACK_DUMB)) return; m->flags &= ~MF_TRACKING; } else { if (Z_mad) { m->flags |= MF_TRACKING; } if ( ( (lc->cavetype == LOC_STAIRS_U) || (lc->cavetype == LOC_STAIRS_D) ) && onein(5) ) { takestairs(m); return; } x = lc->x + rnd(3) - 1; y = lc->y + rnd(3) - 1; if ((x < 0) || (y < 0) || (x >= LEV_X) || (y >= LEV_Y)) { if (lc->on->flags & LVF_WRAPAROUND) { x = (x + LEV_X) % LEV_X; y = (y + LEV_Y) % LEV_Y; } else { panic("Outside level"); } } to = &lc->on->cells[x][y]; if ((to->type == LOC_CAVE) && !to->monst) { movemon(m,to); } } } /* * The standard human-intelligence monster tick handler. Of course, * this is not as intelligent as a real human.... */ void std_human_tick(MONST *m) { LOC *lc; int x; int y; LOC *to; m->age ++; gainhp(m,m->heal); lc = m->loc; if (lc->flags & LF_VISIBLE) { m->flags |= MF_SAWYOU; m->you.x = you->loc->x; m->you.y = you->loc->y; } if (m->age > 1000) { m->flags |= MF_TRACKING; } if (m->flags & MF_SAWYOU) { if (headfor(m,m->you.x,m->you.y,1)) return; if ((lc->cavetype == LOC_STAIRS_U) || (lc->cavetype == LOC_STAIRS_D)) { takestairs(m); } m->flags &= ~MF_SAWYOU; return; } if (lc->objs.inv && rnd(3)) { int t; INVOBJ *picked; int foo(INVOBJ *io) { t = io->dispn; if (rnd(t) < io->dispn) picked = io; return(0); } t = 0; inv_scan(&lc->objs,&foo); if (! picked) panic("can't find an object to pick up"); if (picked->dispn > 1) picked = inventory_split_n(picked,-1,-1); inv_move_1(&lc->objs,picked,&m->invent); } if (m->flags & MF_TRACKING) { if (followtrack(m,TRACK_SMART)) return; m->flags &= ~MF_TRACKING; } else { if (Z_mad) { m->flags |= MF_TRACKING; } if ( ( (lc->cavetype == LOC_STAIRS_U) || (lc->cavetype == LOC_STAIRS_D) ) && onein(5) ) { takestairs(m); return; } x = lc->x + rnd(3) - 1; y = lc->y + rnd(3) - 1; if ((x < 0) || (y < 0) || (x >= LEV_X) || (y >= LEV_Y)) { if (lc->on->flags & LVF_WRAPAROUND) { x = (x + LEV_X) % LEV_X; y = (y + LEV_Y) % LEV_Y; } else { panic("Outside level"); } } to = &lc->on->cells[x][y]; if ((to->type == LOC_CAVE) && !to->monst) { movemon(m,to); } } } /* * The standard take-damage handler. Returns true if the monster died. */ int std_takedamage(MONST *m, int dmg) { m->hp -= dmg; if (m->hp <= 0) { killmon(m); return(1); } return(0); } /* * Go through all monsters doing cleanup, such as destroying dead * monsters, and verification, such as checking that mchain_count is * correct. * * This does not need to run every tick; just every once in a while is * plenty. */ static void tick_monsters(long int arg_li __attribute__((__unused__)), void *arg_vp __attribute__((__unused__))) { MONST **mp; MONST *m; int count; mp = &mchain; count = 0; while (1) { m = *mp; if (! m) break; if (m->flags & MF_DEAD) { *mp = m->link; (*m->ops->destroy)(m); mchain_count --; continue; } count ++; if (count > mchain_count) panic("too many monsters on chain"); mp = &m->link; } if (count != mchain_count) panic("too few monsters on chain"); addfuse_rel(&tick_monsters,0,0,10*TIMESCALE); } /* * Convert a spawn-probability init vector to an array of (relative) * probabilities. */ static void probinit_to_probs(MTINFO *mi) { char set[N_LEVELS]; int i; PROBINIT *pi; int j; for (i=N_LEVELS-1;i>=0;i--) set[i] = 0; for <"init"> (i=0;;i++) { pi = &mi->probinit[i]; switch (pi->kind) { default: panic("bad kind %d in probinit_to_prob",(int)pi->kind); break; case PIK_END: break <"init">; break; case PIK_LEV: if ( (pi->level < -LEVEL_OFFSET) || (pi->nlevel < 1) || (pi->level > DUNGEON_LEVELS) || (pi->nlevel > N_LEVELS) || (pi->level+pi->nlevel > DUNGEON_LEVELS) ) { panic("bad PIK_LEVELS (%d %d) in probinit_to_prob",pi->level,pi->nlevel); } for (j=pi->nlevel-1;j>=0;j--) { mi->probs[LEVEL_OFFSET+pi->level+j] = pi->prob; set[LEVEL_OFFSET+pi->level+j] = 1; } break; } } for (i=N_LEVELS-1;i>=0;i--) if (! set[i]) panic("no prob for %d in probinit_to_prob",i); } /* * Initialize the monster support. */ void initmonst(void) { int i; MTINFO *mi; int *pv; pv = malloc(MON__N*N_LEVELS*sizeof(int)); for (i=0;iprobs = pv; pv += N_LEVELS; probinit_to_probs(mi); } mchain = 0; mchain_count = 0; tick_monsters(0,0); } /* * Make a monster gain hit points. */ void gainhp(MONST *m, int hp) { m->hp += hp; if (m->hp > m->maxhp) m->hp = m->maxhp; }