/* * 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 "gates.h" #include "fuses.h" #include "param.h" #include "effect.h" #include "display.h" #include "structs.h" #include "obj-map.h" #include "objtypes.h" #include "montypes.h" #include "obj-ring.h" #include "mon-@-you.h" #include "digdungeon.h" #include "obj-weapon.h" #include "obj-armour.h" #include "mon.h" typedef struct mspawn MSPAWN; /* * Description of monster spawning probabilities. * * Each level has a vector of (relative) probabilities and monster * types. This list is terminated with a zero probability. */ struct mspawn { int prob; int type; } ; static MSPAWN spawn_most[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 0 } }; static MSPAWN spawn_elemental[] = { { 0 } }; #if (DUNGEON_LEVELS != 25) || (N_LEVELS != 31) #error "Monster spawn setup assumes 25 dungeon and 6 special levels" #endif static MSPAWN *spawn[N_LEVELS] = { [LEVEL_OFFSET+L_EARTH] = &spawn_elemental[0], [LEVEL_OFFSET+L_AIR] = &spawn_elemental[0], [LEVEL_OFFSET+L_FIRE] = &spawn_elemental[0], [LEVEL_OFFSET+L_WATER] = &spawn_elemental[0], [LEVEL_OFFSET+L_UNDERWORLD] = &spawn_most[0], [LEVEL_OFFSET+L_HELL] = &spawn_most[0], [LEVEL_OFFSET-1+1] = &spawn_most[0], [LEVEL_OFFSET-1+2] = &spawn_most[0], [LEVEL_OFFSET-1+3] = &spawn_most[0], [LEVEL_OFFSET-1+4] = &spawn_most[0], [LEVEL_OFFSET-1+5] = &spawn_most[0], [LEVEL_OFFSET-1+6] = &spawn_most[0], [LEVEL_OFFSET-1+7] = &spawn_most[0], [LEVEL_OFFSET-1+8] = &spawn_most[0], [LEVEL_OFFSET-1+9] = &spawn_most[0], [LEVEL_OFFSET-1+10] = &spawn_most[0], [LEVEL_OFFSET-1+11] = &spawn_most[0], [LEVEL_OFFSET-1+12] = &spawn_most[0], [LEVEL_OFFSET-1+13] = &spawn_most[0], [LEVEL_OFFSET-1+14] = &spawn_most[0], [LEVEL_OFFSET-1+15] = &spawn_most[0], [LEVEL_OFFSET-1+16] = &spawn_most[0], [LEVEL_OFFSET-1+17] = &spawn_most[0], [LEVEL_OFFSET-1+18] = &spawn_most[0], [LEVEL_OFFSET-1+19] = &spawn_most[0], [LEVEL_OFFSET-1+20] = &spawn_most[0], [LEVEL_OFFSET-1+21] = &spawn_most[0], [LEVEL_OFFSET-1+22] = &spawn_most[0], [LEVEL_OFFSET-1+23] = &spawn_most[0], [LEVEL_OFFSET-1+24] = &spawn_most[0], [LEVEL_OFFSET-1+25] = &spawn_most[0] }; /* * 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; int i; m = malloc(sizeof(MONST)); m->type = &montypes[type]; m->symbol = '\\'; m->hp = 0; m->maxhp = 0; m->heal = m->type->info->baseheal; m->flags = 0; m->speed = m->type->info->basespeed; m->age = 0; m->created = curtime; m->fuseid = 0; m->you.x = 0; m->you.y = 0; m->priv = 0; m->link = 0; m->ops = 0; inv_init(&m->invent,IT_MON,m); m->lastloc = 0; m->loc = 0; m->effects = 0; for (i=MAXRINGS-1;i>=0;i--) m->rings[i] = 0; m->armour = 0; m->weapon = 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. * * If we can't find a place for the monster, we eventually give up and * skip creating it. */ static void makemon(void) { int probs[N_LEVELS]; int tprob; int i; LEVEL *lv; MONST *m; int dlevel; int mlevel; int countdown; LOC *mloc; MSPAWN *sp; int ckloc(LOC *lc) { if ( (lc->type == LOC_CAVE) && !(lc->flags & LF_VISIBLE) && !lc->monst ) return(1); return(1>--countdown); } 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; mlevel = Z_mad ? DUNGEON_LEVELS-1 : i; lv = &Levels[dlevel]; countdown = 10000; mloc = randomloc(lv,&ckloc); if (countdown < 1) return; sp = spawn[dlevel]; tprob = 0; for (i=0;sp[i].prob;i++) tprob += sp[i].prob; if (tprob < 1) return; tprob = rnd(tprob); for (i=0;sp[i].prob;i++) { tprob -= sp[i].prob; if (tprob < 0) break; } if (! sp[i].prob) panic("can't find monster in makemon (tprob = %d)",tprob); m = newmon(sp[i].type,lv); if (placemon(m,mloc)) { 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)+subtick()); 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 monsters: destroy its action fuse, remove * any rings it's wearing, drop its inventory, clear it out of its * location, update that location, destroy any status effects on it, * and mark it dead (and thus to be finally destroyed next time * through tick_monsters). * * In particular, this does not actually destroy the MONST. This is * important because there is code that uses a MONST pointer after * killing the mosnter, such as mhitm(). */ void std_kill(MONST *m) { cancelfuse(m->fuseid); ring_remove_all(m); mergeinv(&m->invent,&m->loc->objs); m->loc->monst = 0; m->loc->on->nmonst --; upddisp1(m->loc); effect_death(m); 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). But first drop any ungateable objects * the monster is carrying - unless the monster is the player. * * This assumes that we are entered with the monster still in its * pre-gate location. */ void gatemon(MONST *m, LOC *to) { INVOBJ *io; if (m != you) { while ((io = inv_scan(&m->invent,&invtest_ungateable))) { inv_move_1(&m->invent,io,&m->loc->objs); } } 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] && occupiable(lc2) && ( onein(5) || (lc2->tracks[tracktype] == b->tracks[tracktype]) ) ) { n ++; if (onein(n)) b = lc2; } } } } } if (! b->monst) { if (b->type == LOC_GATE) { // XXX fix this when elementals are implemented! gatemon(m,gate_other_end(b,GH_REGULAR)); } 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 && occupiable(lc)) { 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 && occupiable(lc)) { 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 (lc->objs.inv && rnd(2)) { mergeinv(&lc->objs,&m->invent); return; } 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(4)) { mergeinv(&lc->objs,&m->invent); return; } 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 a TDRV indicating (a) how * much damage was actually delivered and (b) whether the monster * died. */ TDRV std_takedamage(MONST *m, int dmg) { TDRV rv; if (dmg > m->hp) { rv.taken = m->hp; rv.flags = TDRV_DIED; m->hp = 0; killmon(m); } else { rv.taken = dmg; rv.flags = 0; m->hp -= dmg; } return(rv); } /* * 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); } /* * Initialize the monster support. */ void initmonst(void) { int i; for (i=0;imtype = i; 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; } /* * Test whether viewer can see viewed. The requirement that viewer * must equal you is here because we don't have anything like * LF_VISIBLE for other monsters. */ int monst_cansee_monst(MONST *viewer, MONST *viewed) { if (viewer != you) panic("unsupported monst_cansee_monst call"); return( (viewed == viewer) || (viewer->flags & MF_TELEPATHIC) || ( ( (viewed->loc->flags & LF_VISIBLE) || ( (viewer->loc->type == LOC_CAVE) && ( (viewer->loc->cavetype == LOC_STAIRS_U) || (viewer->loc->cavetype == LOC_STAIRS_D) ) && (viewed->loc == viewer->loc->to) ) ) && ( !(viewed->flags & MF_INVISIBLE) || (viewer->flags & MF_SEE_INVISIBLE) ) ) ); } void stop_using(MONST *m, INVOBJ *io) { switch (objtypes[io->v[0]->type].class) { case OC_WEAPON: weapon_stop_using(m,io); break; case OC_ARMOUR: armour_stop_using(m,io); break; case OC_RING: ring_stop_using(m,io); break; case OC_MAP: map_stop_using(m,io); break; } } static int roll_bonuses(BONUSROLL *br) { int sum; sum = 0; for (;br;br=br->flink) sum += roll(br->roll); return(sum); } int mon_extra_hit(MONST *m) { return(weapon_hit_bonus(m->weapon)+roll_bonuses(m->bonus_hit)); } int mon_extra_def(MONST *m) { return(armour_def_bonus(m->armour)+roll_bonuses(m->bonus_def)); } int mon_extra_dmg(MONST *m) { return(weapon_dmg_bonus(m->weapon)+roll_bonuses(m->bonus_dmg)); } BONUSROLL *add_bonus(const char *rollstr, BONUSROLL **list, void (*done)(BONUSROLL *)) { BONUSROLL *br; br = malloc(sizeof(BONUSROLL)); br->flink = *list; br->blink = 0; if (*list) (*list)->blink = br; *list = br; br->list = list; br->roll = rollstr; br->teardown = done; return(br); } void remove_bonus(BONUSROLL *br) { if (br->flink) br->flink->blink = br->blink; if (br->blink) br->blink->flink = br->flink; else *br->list = br->flink; (*br->teardown)(br); free(br); }