/* * Monster common code and support. */ #include #include #include "obj.h" #include "vars.h" #include "dice.h" #include "trap.h" #include "util.h" #include "fight.h" #include "pline.h" #include "gates.h" #include "fuses.h" #include "param.h" #include "effect.h" #include "damage.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_1[] = { { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_2[] = { { 1, MON_URCHIN }, { 2, MON_RAT }, { 2, MON_LIZARD }, { 0 } }; static MSPAWN spawn_3[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_4[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_5[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_6[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_7[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_8[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_9[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_10[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_11[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_12[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_13[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_14[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_15[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_16[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_17[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_18[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_19[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_20[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_21[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 0 } }; static MSPAWN spawn_22[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 2, MON_CLAY_GOLEM }, { 0 } }; static MSPAWN spawn_23[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 2, MON_CLAY_GOLEM }, { 0 } }; static MSPAWN spawn_24[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 2, MON_CLAY_GOLEM }, { 0 } }; static MSPAWN spawn_25[] = { { 1, MON_URCHIN }, { 1, MON_RAT }, { 1, MON_LIZARD }, { 2, MON_CLAY_GOLEM }, { 0 } }; static MSPAWN spawn_elemental_e[] = { { 0 } }; static MSPAWN spawn_elemental_a[] = { { 0 } }; static MSPAWN spawn_elemental_f[] = { { 0 } }; static MSPAWN spawn_elemental_w[] = { { 0 } }; static MSPAWN spawn_underworld[] = { { 0 } }; static MSPAWN spawn_hell[] = { { 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_e[0], [LEVEL_OFFSET+L_AIR] = &spawn_elemental_a[0], [LEVEL_OFFSET+L_FIRE] = &spawn_elemental_f[0], [LEVEL_OFFSET+L_WATER] = &spawn_elemental_w[0], [LEVEL_OFFSET+L_UNDERWORLD] = &spawn_underworld[0], [LEVEL_OFFSET+L_HELL] = &spawn_hell[0], [LEVEL_OFFSET-1+1] = &spawn_1[0], [LEVEL_OFFSET-1+2] = &spawn_2[0], [LEVEL_OFFSET-1+3] = &spawn_3[0], [LEVEL_OFFSET-1+4] = &spawn_4[0], [LEVEL_OFFSET-1+5] = &spawn_5[0], [LEVEL_OFFSET-1+6] = &spawn_6[0], [LEVEL_OFFSET-1+7] = &spawn_7[0], [LEVEL_OFFSET-1+8] = &spawn_8[0], [LEVEL_OFFSET-1+9] = &spawn_9[0], [LEVEL_OFFSET-1+10] = &spawn_10[0], [LEVEL_OFFSET-1+11] = &spawn_11[0], [LEVEL_OFFSET-1+12] = &spawn_12[0], [LEVEL_OFFSET-1+13] = &spawn_13[0], [LEVEL_OFFSET-1+14] = &spawn_14[0], [LEVEL_OFFSET-1+15] = &spawn_15[0], [LEVEL_OFFSET-1+16] = &spawn_16[0], [LEVEL_OFFSET-1+17] = &spawn_17[0], [LEVEL_OFFSET-1+18] = &spawn_18[0], [LEVEL_OFFSET-1+19] = &spawn_19[0], [LEVEL_OFFSET-1+20] = &spawn_20[0], [LEVEL_OFFSET-1+21] = &spawn_21[0], [LEVEL_OFFSET-1+22] = &spawn_22[0], [LEVEL_OFFSET-1+23] = &spawn_23[0], [LEVEL_OFFSET-1+24] = &spawn_24[0], [LEVEL_OFFSET-1+25] = &spawn_25[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->baseflags & MBF_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) { if (m->baseflags & MBF_DEAD) return; 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->magiconly = 0; m->timeonly = 0; m->heal = m->type->info->baseheal; m->lastheal_time = curtime; m->lastheal_magic = curtime; m->baseflags = 0; m->effflags = 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->bonus_def = 0; m->bonus_hit = 0; m->bonus_dmg = 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); } } /* * Try to find an empty spot next to a location. This may actually * pick a spot 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 LOC *findnextto(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 findnextto?"); } if (! gotlc) { panic("findnextto: have cell but can't find it?"); } return(gotlc); } /* * Make a new monster. * * This is used two ways. When called from makemon_fuse, near is nil, * and the monster may be created on any level (though with a heavy * bias in favour of the level the player is on). When called by a * scroll of summon monster, near is the player location, and the * created monster is placed as near as it can be to that. * * 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. */ MONST *makemon(LOC *near) { 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); } if (near) { for (i=0;ion->index] = 1; tprob = 1; } else { 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(0); } 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(0); sp = spawn[dlevel]; tprob = 0; for (i=0;sp[i].prob;i++) tprob += sp[i].prob; if (tprob < 1) return(0); 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 (near) { i = placemon(m,findnextto(near)); } else { i = placemon(m,mloc); } if (i) { pline("makemon: can't place monster"); return(0); } return(m); } /* * 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(0); } /* * 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 monster, 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->baseflags |= MBF_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 ++; maybe_trap(m); (*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. See findnextto for more. */ static void monnextto(MONST *m, LOC *to) { movemon(m,findnextto(to)); } /* * 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->type == LOC_WALL) || (b->type == LOC_ROCK)) && !(m->effflags & MEF_PHASING)) { return(0); } 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 ++; m->speed = (m->effflags & MEF_SPEED) ? m->type->info->basespeed / 2 : m->type->info->basespeed; gainhp(m,m->heal,GHP_TIME); lc = m->loc; if (lc->flags & LF_VISIBLE) { m->baseflags |= MBF_SAWYOU; m->you.x = you->loc->x; m->you.y = you->loc->y; } if (m->age > 1000) { m->baseflags |= MBF_TRACKING; } if (m->baseflags & MBF_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->baseflags &= ~MBF_SAWYOU; } else if (m->baseflags & MBF_TRACKING) { if (followtrack(m,TRACK_DUMB)) return; m->baseflags &= ~MBF_TRACKING; } else { if (Z_mad) { m->baseflags |= MBF_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,GHP_TIME); m->speed = (m->effflags & MEF_SPEED) ? m->type->info->basespeed / 2 : m->type->info->basespeed; lc = m->loc; if (lc->flags & LF_VISIBLE) { m->baseflags |= MBF_SAWYOU; m->you.x = you->loc->x; m->you.y = you->loc->y; } if (m->age > 1000) { m->baseflags |= MBF_TRACKING; } if (m->baseflags & MBF_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->baseflags &= ~MBF_SAWYOU; return; } if (lc->objs.inv && rnd(4)) { mergeinv(&lc->objs,&m->invent); return; } if (m->baseflags & MBF_TRACKING) { if (followtrack(m,TRACK_SMART)) return; m->baseflags &= ~MBF_TRACKING; } else { if (Z_mad) m->baseflags |= MBF_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, DAMAGE dmg) { TDRV rv; DMGELT *e; int d; rv.taken = 0; if (m->effflags & MEF_INVULNERABLE) { rv.flags = 0; return(rv); } for (e=dmg.elts;e;e=e->link) { d = e->hp; switch (e->kind) { case DK_ORDINARY: break; case DK_FIRE: if (m->effflags & MEF_RESIST_FIRE) d >>= 1; break; case DK_COLD: if (m->effflags & MEF_RESIST_COLD) d >>= 1; break; case DK_ELECTRIC: // XXX resist electricity someday? break; default: panic("impossible damage kind %d",(int)e->kind); break; } if (d >= m->hp) { rv.taken += m->hp; rv.flags = TDRV_DIED; m->hp = 0; killmon(m); return(rv); } rv.taken += d; m->hp -= d; switch (e->flags & (DF_MAGICONLY|DF_TIMEONLY)) { case 0: break; case DF_MAGICONLY: m->magiconly += d; break; case DF_TIMEONLY: m->timeonly += d; break; default: panic("impossible flags in std_takedamage"); break; } } damage_free(dmg); rv.flags = 0; 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->baseflags & MBF_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, GHPHOW how) { int n; switch (how) { default: panic("bad how %d to gainhp",(int)how); break; case GHP_TIME: n = (m->timeonly < hp) ? m->timeonly : hp; m->timeonly -= n; m->hp += hp; if (m->hp > m->maxhp - m->magiconly) m->hp = m->maxhp - m->magiconly; break; case GHP_MAGIC: n = (m->magiconly < hp) ? m->magiconly : hp; m->magiconly -= n; m->hp += hp; if (m->hp > m->maxhp - m->timeonly) m->hp = m->maxhp - m->timeonly; break; } } /* * 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( (viewer->baseflags & MBF_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->baseflags & MBF_INVISIBLE) || (viewed->effflags & MEF_INVISIBLE) ) || (viewer->effflags & MEF_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)); } DAMAGE mon_extra_dmg(MONST *m) { return(damage_simple(weapon_dmg_bonus(m->weapon)+roll_bonuses(m->bonus_dmg),DK_ORDINARY,0)); } 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); } /* * Give one monster (m) experience for killing another (dec). At * present, only the player actually gains experience. */ void gainexp(MONST *m, MONST *dec) { if (m == you) you_gain_exp(dec->type->info->exp); } /* * Common code for time-based healing. * * This is complicated because of multiple desires. In particular, we * want * * - Support for monster actions taking sub-tick amounts of time, * with fairly fine granularity. * * - Healing to work "as if" we gain one hit point every N time * units, where N is TIMESCALE/GHP_TIME (or half that if * hasted). Given how fine-grained TIMESCALE is, we accept an * N that's an integer even if it ideally wouldn't be. * * So, what we do is to record curtime as of the last time we * conceptually gained a hit point. This is not ideal, as hit points * are still dispensed only when the monster gets an action; ideally, * they would be gained uniformly throughout time. We tolerate this * because time-based healing is such a low rate that this would make * a difference in only the rarest of cases. * * Arglist: (m,lasttime,hp,how) * m is the affected monster * lasttime is the last-heal time * hp is the healing rate, in hit points per full tick * how is the GHPHOW for this healing * Return value: amount by which last-heal time should be advanced */ static TIME time_based_healing(MONST *m, TIME lasttime, int hp, GHPHOW how) { TIME d; int n; d = curtime - lasttime; n = (hp * d) / TIMESCALE; gainhp(m,n,how); return((n*TIMESCALE)/hp); } /* * Main routine for time-based healing. We support two kinds of * time-based healing, a per-monster healing rate (m->heal hp per * tick) and regeneration (always 50 hp per tick). So we just call * time_based_healing twice, once for each. * * If this routine is modified or removed, make sure to check the * comment on struct monst to see if it is still accurate. */ void time_healing(MONST *m) { m->lastheal_time += time_based_healing(m,m->lastheal_time,m->heal,GHP_TIME); if (m->effflags & MEF_REGENERATION) { m->lastheal_magic += time_based_healing(m,m->lastheal_magic,50,GHP_MAGIC); } else { m->lastheal_magic = curtime; } } /* * Return true iff there's a monster on a cell adjacent to the argument * LOC. Handles LVF_WRAPAROUND. A monster on the argument cell does * not count as next to it. */ int monst_next_to(LOC *l) { XY d; int i; LOC *l2; d.x = 1; d.y = 0; for (i=8;i>0;i--) { l2 = movecell(l,d.x,d.y,0); if (! l2) continue; if (l2->monst) return(1); d = turn_r45(d); } return(0); }