home *** CD-ROM | disk | FTP | other *** search
- /* SCCS Id: @(#)worm.c 3.1 91/12/30 */
- /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
- /* NetHack may be freely redistributed. See license for details. */
-
- #include "hack.h"
- #include "lev.h"
-
- #define newseg() (struct wseg *) alloc(sizeof(struct wseg))
- #define dealloc_seg(wseg) free((genericptr_t) (wseg))
-
- /* worm segment structure */
- struct wseg {
- struct wseg *nseg;
- xchar wx, wy; /* the segment's position */
- };
-
- static void FDECL(toss_wsegs, (struct wseg *,BOOLEAN_P));
- static void FDECL(shrink_worm, (int));
- static void FDECL(random_dir, (XCHAR_P,XCHAR_P,xchar *,xchar *));
- static struct wseg *FDECL(create_worm_tail, (int));
-
- /* Description of long worm implementation.
- *
- * Each monst struct of the head of a tailed worm has a wormno set to
- * 1 <= wormno < MAX_NUM_WORMS
- * If wormno == 0 this does not mean that the monster is not a worm,
- * it just means that the monster does not have a long worm tail.
- *
- * The actual segments of a worm are not full blown monst structs.
- * They are small wseg structs, and their position in the levels.monsters[][]
- * array is held by the monst struct of the head of the worm. This makes
- * things like probing and hit point bookkeeping much easier.
- *
- * The segments of the long worms on a level are kept as an array of
- * singly threaded linked lists. The wormno variable is used as an index
- * for these segment arrays.
- *
- * wtails: The first (starting struct) of a linked list. This points
- * to the tail (last) segment of the worm.
- *
- * wheads: The last (end) of a linked list of segments. This points to
- * the segment that is at the same position as the real monster
- * (the head). Note that the segment that wheads[wormno] points
- * to, is not displayed. It is simply there to keep track of
- * where the head came from, so that worm movement and display are
- * simplified later.
- * Keeping the head segment of the worm at the end of the list
- * of tail segments is an endless source of confusion, but it is
- * necessary.
- * From now on, we will use "start" and "end" to refer to the
- * linked list and "head" and "tail" to refer to the worm.
- *
- * One final worm array is:
- *
- * wgrowtime: This tells us when to add another segment to the worm.
- *
- * When a worm is moved, we add a new segment at the head, and delete the
- * segment at the tail (unless we want it to grow). This new head segment is
- * located in the same square as the actual head of the worm. If we want
- * to grow the worm, we don't delete the tail segment, and we give the worm
- * extra hit points, which possibly go into its maximum.
- *
- * Non-moving worms (worm_nomove) are assumed to be surrounded by their own
- * tail, and, thus, shrink instead of grow (as their tails keep going while
- * their heads are stopped short). In this case, we delete the last tail
- * segment, and remove hit points from the worm.
- */
-
- struct wseg *wheads[MAX_NUM_WORMS] = DUMMY, *wtails[MAX_NUM_WORMS] = DUMMY;
- long wgrowtime[MAX_NUM_WORMS] = DUMMY;
-
- /*
- * get_wormno()
- *
- * Find an unused worm tail slot and return the index. A zero means that
- * there are no slots available. This means that the worm head can exist,
- * it just cannot ever grow a tail.
- *
- * It, also, means that there is an optimisation to made. The [0] positions
- * of the arrays are never used. Meaning, we really *could* have one more
- * tailed worm on the level, or use a smaller array (using wormno - 1).
- *
- * Implementation is left to the interested hacker.
- */
- int
- get_wormno()
- {
- register int new_wormno = 1;
-
- while (new_wormno < MAX_NUM_WORMS) {
- if (!wheads[new_wormno])
- return new_wormno; /* found an empty wtails[] slot at new_wormno */
- new_wormno++;
- }
-
- return(0); /* level infested with worms */
- }
-
- /*
- * initworm()
- *
- * Use if (mon->wormno = get_wormno()) before calling this function!
- *
- * Initialize the worm entry. This will set up the worm grow time, and
- * create and initialize the dummy segment for wheads[] and wtails[].
- *
- * If the worm has no tail (ie get_wormno() fails) then this function need
- * not be called.
- */
- void
- initworm(worm, wseg_count)
- struct monst *worm;
- int wseg_count;
- {
- register struct wseg *seg, *new_tail = create_worm_tail(wseg_count);
- register int wnum = worm->wormno;
-
- /* if (!wnum) return; /* bullet proofing */
-
- if (new_tail) {
- wtails[wnum] = new_tail;
- for (seg = new_tail; seg->nseg; seg = seg->nseg);
- wheads[wnum] = seg;
- } else {
- wtails[wnum] = wheads[wnum] = seg = newseg();
- seg->nseg = (struct wseg *) 0;
- seg->wx = worm->mx;
- seg->wy = worm->my;
- }
- wgrowtime[wnum] = 0L;
- }
-
-
- /*
- * toss_wsegs()
- *
- * Get rid of all worm segments on and following the given pointer curr.
- * The display may or may not need to be updated as we free the segments.
- */
- static
- void
- toss_wsegs(curr, display_update)
- register struct wseg *curr;
- register boolean display_update;
- {
- register struct wseg *seg;
-
- while (curr) {
- seg = curr->nseg;
-
- /* remove from level.monsters[][] */
-
- /* need to check curr->wx for genocided while migrating_mon */
- if (curr->wx) {
- remove_monster(curr->wx, curr->wy);
-
- /* update screen before deallocation */
- if (display_update) newsym(curr->wx,curr->wy);
- }
-
- /* free memory used by the segment */
- dealloc_seg(curr);
- curr = seg;
- }
- }
-
-
- /*
- * shrink_worm()
- *
- * Remove the tail segment of the worm (the starting segment of the list).
- */
- static
- void
- shrink_worm(wnum)
- int wnum; /* worm number */
- {
- struct wseg *seg;
-
- if (wtails[wnum] == wheads[wnum]) return; /* no tail */
-
- seg = wtails[wnum];
- wtails[wnum] = seg->nseg;
- seg->nseg = (struct wseg *) 0;
- toss_wsegs(seg, TRUE);
- }
-
- /*
- * worm_move()
- *
- * Check for mon->wormno before calling this function!
- *
- * Move the worm. Maybe grow.
- */
- void
- worm_move(worm)
- struct monst *worm;
- {
- register struct wseg *seg, *new_seg; /* new segment */
- register int wnum = worm->wormno; /* worm number */
-
-
- /* if (!wnum) return; /* bullet proofing */
-
- /*
- * Place a segment at the old worm head. The head has already moved.
- */
- seg = wheads[wnum];
- place_worm_seg(worm, seg->wx, seg->wy);
- newsym(seg->wx,seg->wy); /* display the new segment */
-
- /*
- * Create a new dummy segment head and place it at the end of the list.
- */
- new_seg = newseg();
- new_seg->wx = worm->mx;
- new_seg->wy = worm->my;
- new_seg->nseg = (struct wseg *) 0;
- seg->nseg = new_seg; /* attach it to the end of the list */
- wheads[wnum] = new_seg; /* move the end pointer */
-
-
- if (wgrowtime[wnum] <= moves) {
- if (!wgrowtime[wnum])
- wgrowtime[wnum] = moves + rnd(5);
- else
- wgrowtime[wnum] += rn1(15, 3);
- worm->mhp += 3;
- if (worm->mhp > MHPMAX) worm->mhp = MHPMAX;
- if (worm->mhp > worm->mhpmax) worm->mhpmax = worm->mhp;
- } else
- /* The worm doesn't grow, so the last segment goes away. */
- shrink_worm(wnum);
- }
-
- /*
- * worm_nomove()
- *
- * Check for mon->wormno before calling this function!
- *
- * The worm don't move so it should shrink.
- */
- void
- worm_nomove(worm)
- register struct monst *worm;
- {
- shrink_worm((int) worm->wormno); /* shrink */
-
- if (worm->mhp > 3)
- worm->mhp -= 3; /* mhpmax not changed ! */
- else
- worm->mhp = 1;
- }
-
- /*
- * wormgone()
- *
- * Check for mon->wormno before calling this function!
- *
- * Kill a worm tail.
- */
- void
- wormgone(worm)
- register struct monst *worm;
- {
- register int wnum = worm->wormno;
-
- /* if (!wnum) return; /* bullet proofing */
-
- worm->wormno = 0;
-
- /* This will also remove the real monster (ie 'w') from the its
- * position in level.monsters[][].
- */
- toss_wsegs(wtails[wnum], TRUE);
-
- wheads[wnum] = wtails[wnum] = (struct wseg *) 0;
- }
-
- /*
- * wormhitu()
- *
- * Check for mon->wormno before calling this function!
- *
- * If the hero is near any part of the worm, the worm will try to attack.
- */
- void
- wormhitu(worm)
- register struct monst *worm;
- {
- register int wnum = worm->wormno;
- register struct wseg *seg;
-
- /* if (!wnum) return; /* bullet proofing */
-
- /* This does not work right now because mattacku() thinks that the head is
- * out of range of the player. We might try to kludge, and bring the head
- * within range for a tiny moment, but this needs a bit more looking at
- * before we decide to do this.
- */
- for (seg = wtails[wnum]; seg; seg = seg->nseg)
- if (distu(seg->wx, seg->wy) < 3)
- (void) mattacku(worm);
- }
-
- /* cutworm()
- *
- * Check for mon->wormno before calling this function!
- *
- * When hitting a worm (worm) at position x, y, with a weapon (weap),
- * there is a chance that the worm will be cut in half, and a chance
- * that both halves will survive.
- */
- void
- cutworm(worm, x, y, weap)
- struct monst *worm;
- xchar x,y;
- struct obj *weap;
- {
- register struct wseg *curr, *new_tail;
- register struct monst *new_worm;
- int wnum = worm->wormno;
- int cut_chance, new_wnum;
-
- /* if (!wnum) return; /* bullet proofing */
-
- if (x == worm->mx && y == worm->my) return; /* hit on head */
-
- /* cutting goes best with a bladed weapon */
- cut_chance = rnd(20); /* Normally 1-16 does not cut */
- /* Normally 17-20 does */
-
- if (weap && is_blade(weap)) /* With a blade 1- 6 does not cut */
- cut_chance += 10; /* 7-20 does */
-
- if (cut_chance < 17) return; /* not good enough */
-
- /* Find the segment that was attacked. */
- curr = wtails[wnum];
-
- while ( (curr->wx != x) || (curr->wy != y) ) {
- curr = curr->nseg;
- if (!curr) {
- impossible("cut_worm: no segment at (%d,%d)", (int) x, (int) y);
- return;
- }
- }
-
- /* If this is the tail segment, then the worm just loses it. */
- if (curr == wtails[wnum]) {
- shrink_worm(wnum);
- return;
- }
-
- /*
- * Split the worm. The tail for the new worm is the old worm's tail.
- * The tail for the old worm is the segment that follows "curr",
- * and "curr" becomes the dummy segment under the new head.
- */
- new_tail = wtails[wnum];
- wtails[wnum] = curr->nseg;
- curr->nseg = (struct wseg *) 0; /* split the worm */
-
- /*
- * At this point, the old worm is correct. Any new worm will have
- * it's head at "curr" and its tail at "new_tail".
- */
-
- /* Sometimes the tail end dies. */
- if (rn2(3) || !(new_wnum = get_wormno())) {
- You("cut part of the tail off of %s.", mon_nam(worm));
- toss_wsegs(new_tail, TRUE);
- worm->mhp /= 2;
- return;
- }
-
- /* Create the second worm. */
- new_worm = newmonst(0);
- *new_worm = *worm; /* make a copy of the old worm */
- new_worm->m_id = flags.ident++; /* make sure it has a unique id */
- new_worm->wormno = new_wnum; /* affix new worm number */
-
- if (worm->mtame)
- new_worm->mtame = (rn2(max(2 + u.uluck, 2)) ? worm->mtame : 0);
- else
- if (worm->mpeaceful)
- new_worm->mpeaceful = (rn2(max(2 + u.uluck, 2)) ? 1 : 0);
- set_malign(new_worm);
-
- new_worm->mxlth = new_worm->mnamelth = 0;
-
- /* Devalue the monster level of both halves of the worm. */
- worm->m_lev = ((unsigned)worm->m_lev <= 3) ?
- (unsigned)worm->m_lev : max((unsigned)worm->m_lev - 2, 3);
- new_worm->m_lev = worm->m_lev;
-
- /* Calculate the mhp on the new_worm for the (lower) monster level. */
- new_worm->mhpmax = new_worm->mhp = d((int)new_worm->m_lev, 8);
-
- /* Calculate the mhp on the old worm for the (lower) monster level. */
- if (worm->m_lev > 3) {
- worm->mhpmax = d((int)worm->m_lev, 8);
- if (worm->mhpmax < worm->mhp) worm->mhp = worm->mhpmax;
- }
-
- /* Add new monster to mon chain. */
- new_worm->nmon = fmon;
- fmon = new_worm;
-
- /* Initialize the new worm. */
- place_monster(new_worm, x, y); /* put worm in level.monsters[][] */
- newsym(x, y); /* make sure new worm shows up */
-
- wtails[new_wnum] = new_tail; /* We've got all the info right now */
- wheads[new_wnum] = curr; /* so we can do this faster than */
- wgrowtime[new_wnum] = 0L; /* trying to call initworm(). */
-
- /* Place the new monster at all the segment locations. */
- place_wsegs(new_worm);
-
- You("cut %s in half.", mon_nam(worm));
- }
-
-
- /*
- * see_wsegs()
- *
- * Refresh all of the segments of the given worm. This is only called
- * from see_monster() in display.c or when a monster goes minvis. It
- * is located here for modularity.
- */
- void
- see_wsegs(worm)
- struct monst *worm;
- {
- struct wseg *curr = wtails[worm->wormno];
-
- /* if (!mtmp->wormno) return; /* bullet proofing */
-
- while (curr != wheads[worm->wormno]) {
- newsym(curr->wx,curr->wy);
- curr = curr->nseg;
- }
- }
-
-
- /*
- * save_worm()
- *
- * Save the worm information for later use. The count is the number
- * of segments, including the dummy. Called from save.c.
- */
- void
- save_worm(fd, mode)
- int fd, mode;
- {
- int i;
- int count;
- struct wseg *curr, *temp;
-
- for (i = 1; i < MAX_NUM_WORMS; i++) {
- for (count = 0, curr = wtails[i]; curr; curr = curr->nseg) count++;
-
- /* Save number of segments */
- bwrite(fd, (genericptr_t) &count, sizeof(int));
-
- /* Save segment locations of the monster. */
- if (count) {
- for (curr = wtails[i]; curr; curr = curr->nseg) {
- bwrite(fd, (genericptr_t) &(curr->wx), sizeof(xchar));
- bwrite(fd, (genericptr_t) &(curr->wy), sizeof(xchar));
- }
- }
- }
- bwrite(fd, (genericptr_t) wgrowtime, sizeof(wgrowtime));
-
- if (mode & FREE_SAVE) {
- /* Free the segments only. savemonchn() will take care of the
- * monsters. */
- for (i = 1; i < MAX_NUM_WORMS; i++) {
- if (!(curr = wtails[i])) continue;
-
- while (curr) {
- temp = curr->nseg;
- dealloc_seg(curr); /* free the segment */
- curr = temp;
- }
- wheads[i] = wtails[i] = (struct wseg *) 0;
- }
- }
-
- }
-
- /*
- * rest_worm()
- *
- * Restore the worm information from the save file. Called from restore.c
- */
- void
- rest_worm(fd)
- int fd;
- {
- int i, j, count;
- struct wseg *curr, *temp;
-
- for (i = 1; i < MAX_NUM_WORMS; i++) {
- mread(fd, (genericptr_t) &count, sizeof(int));
- if (!count) continue; /* none */
-
- /* Get the segments. */
- for (curr = (struct wseg *) 0, j = 0; j < count; j++) {
- temp = newseg();
- temp->nseg = (struct wseg *) 0;
- mread(fd, (genericptr_t) &(temp->wx), sizeof(xchar));
- mread(fd, (genericptr_t) &(temp->wy), sizeof(xchar));
- if (curr)
- curr->nseg = temp;
- else
- wtails[i] = temp;
- curr = temp;
- }
- wheads[i] = curr;
- }
- mread(fd, (genericptr_t) wgrowtime, sizeof(wgrowtime));
- }
-
- /*
- * place_wsegs()
- *
- * Place the segments of the given worm. Called from restore.c
- */
- void
- place_wsegs(worm)
- struct monst *worm;
- {
- struct wseg *curr = wtails[worm->wormno];
-
- /* if (!mtmp->wormno) return; /* bullet proofing */
-
- while (curr != wheads[worm->wormno]) {
- place_worm_seg(worm,curr->wx,curr->wy);
- curr = curr->nseg;
- }
- }
-
- /*
- * remove_worm()
- *
- * This function is equivalent to the remove_monster #define in
- * rm.h, only it will take the worm *and* tail out of the levels array.
- * It does not get rid of (dealloc) the worm tail structures, and it does
- * not remove the mon from the fmon chain.
- */
- void
- remove_worm(worm)
- register struct monst *worm;
- {
- register struct wseg *curr = wtails[worm->wormno];
-
- /* if (!mtmp->wormno) return; /* bullet proofing */
-
- while (curr) {
- remove_monster(curr->wx, curr->wy);
- newsym(curr->wx, curr->wy);
- curr = curr->nseg;
- }
- }
-
- /*
- * place_worm_tail_randomly()
- *
- * Place a worm tail somewhere on a level behind the head.
- * This routine essentially reverses the order of the wsegs from head
- * to tail while placing them.
- * x, and y are most likely the worm->mx, and worm->my, but don't *need* to
- * be, if somehow the head is disjoint from the tail.
- */
- void
- place_worm_tail_randomly(worm, x, y)
- struct monst *worm;
- xchar x, y;
- {
- int wnum = worm->wormno;
- struct wseg *curr = wtails[wnum];
- struct wseg *new_tail;
- register xchar ox = x, oy = y;
-
- /* if (!wnum) return; /* bullet proofing */
-
- if (wnum && (!wtails[wnum] || !wheads[wnum]) ) {
- impossible("place_worm_tail_randomly: wormno is set without a tail!");
- return;
- }
-
- wheads[wnum] = new_tail = curr;
- curr = curr->nseg;
- new_tail->nseg = (struct wseg *) 0;
- new_tail->wx = x;
- new_tail->wy = y;
-
- while(curr) {
- xchar nx, ny;
- char tryct = 0;
-
- /* pick a random direction from x, y and search for goodpos() */
-
- do {
- random_dir(ox, oy, &nx, &ny);
- } while (!goodpos(nx, ny, worm, worm->data) && (tryct++ < 50));
-
- if (tryct < 50) {
- place_worm_seg(worm, nx, ny);
- curr->wx = ox = nx;
- curr->wy = oy = ny;
- wtails[wnum] = curr;
- curr = curr->nseg;
- wtails[wnum]->nseg = new_tail;
- new_tail = wtails[wnum];
- newsym(nx, ny);
- } else { /* Oops. Truncate because there was */
- toss_wsegs(curr, FALSE); /* no place for the rest of it */
- curr = (struct wseg *) 0;
- }
- }
- }
-
- /*
- * Given a coordinate x, y.
- * return in *nx, *ny, the coordinates of one of the <= 8 squares adjoining.
- *
- * This function, and the loop it serves, could be eliminated by coding
- * enexto() with a search radius.
- */
- static
- void
- random_dir(x, y, nx, ny)
- register xchar x, y;
- register xchar *nx, *ny;
- {
- *nx = x;
- *ny = y;
-
- *nx += (x > 1 ? /* extreme left ? */
- (x < COLNO ? /* extreme right ? */
- (rn2(3) - 1) /* neither so +1, 0, or -1 */
- : -rn2(2)) /* 0, or -1 */
- : rn2(2)); /* 0, or 1 */
-
- *ny += (*nx == x ? /* same kind of thing with y */
- (y > 1 ?
- (y < ROWNO ?
- (rn2(2) ?
- 1
- : -1)
- : -1)
- : 1)
- : (y > 1 ?
- (y < ROWNO ?
- (rn2(3) - 1)
- : -rn2(2))
- : rn2(2)));
- }
-
- /* count_wsegs()
- *
- * returns
- * the number of visible segments that a worm has.
- */
-
- int
- count_wsegs(mtmp)
- struct monst *mtmp;
- {
- register int i=0;
- register struct wseg *curr = (wtails[mtmp->wormno])->nseg;
-
- /* if (!mtmp->wormno) return 0; /* bullet proofing */
-
- while (curr) {
- i++;
- curr = curr->nseg;
- }
-
- return i;
- }
-
- /* create_worm_tail()
- *
- * will create a worm tail chain of (num_segs + 1) and return a pointer to it.
- */
- static
- struct wseg *
- create_worm_tail(num_segs)
- int num_segs;
- {
- register int i=0;
- register struct wseg *new_tail, *curr;
-
- if (!num_segs) return (struct wseg *)0;
-
- new_tail = curr = newseg();
- curr->nseg = (struct wseg *)0;
- curr->wx = 0;
- curr->wy = 0;
-
- while (i < num_segs) {
- curr->nseg = newseg();
- curr = curr->nseg;
- curr->nseg = (struct wseg *)0;
- curr->wx = 0;
- curr->wy = 0;
- i++;
- }
-
- return (new_tail);
- }
-
- /*worm.c*/
-