1    | /*	SCCS Id: @(#)detect.c	3.3	1999/12/06	*/
2    | /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3    | /* NetHack may be freely redistributed.  See license for details. */
4    | 
5    | /*
6    |  * Detection routines, including crystal ball, magic mapping, and search
7    |  * command.
8    |  */
9    | 
10   | #include "hack.h"
11   | #include "artifact.h"
12   | 
13   | extern boolean known;	/* from read.c */
14   | 
15   | STATIC_DCL void FDECL(do_dknown_of, (struct obj *));
16   | STATIC_DCL boolean FDECL(check_map_spot, (int,int,CHAR_P));
17   | STATIC_DCL boolean FDECL(clear_stale_map, (CHAR_P));
18   | STATIC_DCL void FDECL(sense_trap, (struct trap *,XCHAR_P,XCHAR_P,int));
19   | STATIC_DCL void FDECL(show_map_spot, (int,int));
20   | STATIC_PTR void FDECL(findone,(int,int,genericptr_t));
21   | STATIC_PTR void FDECL(openone,(int,int,genericptr_t));
22   | 
23   | /* Recursively search obj for an object in class oclass and return 1st found */
24   | struct obj *
25   | o_in(obj, oclass)
26   | struct obj* obj;
27   | char oclass;
28   | {
29   |     register struct obj* otmp;
30   |     struct obj *temp;
31   | 
32   |     if (obj->oclass == oclass) return obj;
33   | 
34   |     if (Has_contents(obj)) {
35   | 	for (otmp = obj->cobj; otmp; otmp = otmp->nobj)
36   | 	    if (otmp->oclass == oclass) return otmp;
37   | 	    else if (Has_contents(otmp) && (temp = o_in(otmp, oclass)))
38   | 		return temp;
39   |     }
40   |     return (struct obj *) 0;
41   | }
42   | 
43   | STATIC_OVL void
44   | do_dknown_of(obj)
45   | struct obj *obj;
46   | {
47   |     struct obj *otmp;
48   | 
49   |     obj->dknown = 1;
50   |     if (Has_contents(obj)) {
51   | 	for(otmp = obj->cobj; otmp; otmp = otmp->nobj)
52   | 	    do_dknown_of(otmp);
53   |     }
54   | }
55   | 
56   | /* Check whether the location has an outdated object displayed on it. */
57   | STATIC_OVL boolean
58   | check_map_spot(x, y, oclass)
59   | int x, y;
60   | register char oclass;
61   | {
62   | 	register int glyph;
63   | 	register struct obj *otmp;
64   | 	register struct monst *mtmp;
65   | 
66   | 	glyph = glyph_at(x,y);
67   | 	if (glyph_is_object(glyph)) {
68   | 	    /* there's some object shown here */
69   | 	    if (oclass == ALL_CLASSES) {
70   | 		return((boolean)( !(level.objects[x][y] ||     /* stale if nothing here */
71   | 			    ((mtmp = m_at(x,y)) != 0 &&
72   | 				(mtmp->mgold || mtmp->minvent)))));
73   | 	    } else if (objects[glyph_to_obj(glyph)].oc_class == oclass) {
74   | 		/* the object shown here is of interest */
75   | 		for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere)
76   | 		    if (o_in(otmp, oclass)) return FALSE;
77   | 		/* didn't find it; perhaps a monster is carrying it */
78   | 		if ((mtmp = m_at(x,y)) != 0) {
79   | 		    if (oclass == GOLD_CLASS && mtmp->mgold)
80   | 			return FALSE;
81   | 		    else for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj)
82   | 			    if (o_in(otmp, oclass)) return FALSE;
83   | 		}
84   | 		/* detection indicates removal of this object from the map */
85   | 		return TRUE;
86   | 	    }
87   | 	}
88   | 	return FALSE;
89   | }
90   | 
91   | /*
92   |    When doing detection, remove stale data from the map display (corpses
93   |    rotted away, objects carried away by monsters, etc) so that it won't
94   |    reappear after the detection has completed.  Return true if noticeable
95   |    change occurs.
96   |  */
97   | STATIC_OVL boolean
98   | clear_stale_map(oclass)
99   | register char oclass;
100  | {
101  | 	register int zx, zy;
102  | 	register boolean change_made = FALSE;
103  | 
104  | 	for (zx = 1; zx < COLNO; zx++)
105  | 	    for (zy = 0; zy < ROWNO; zy++)
106  | 		if (check_map_spot(zx, zy, oclass)) {
107  | 		    unmap_object(zx, zy);
108  | 		    change_made = TRUE;
109  | 		}
110  | 
111  | 	return change_made;
112  | }
113  | 
114  | /* look for gold, on the floor or in monsters' possession */
115  | int
116  | gold_detect(sobj)
117  | register struct obj *sobj;
118  | {
119  |     register struct obj *obj;
120  |     register struct monst *mtmp;
121  |     int uw = u.uinwater;
122  |     struct obj *temp;
123  |     boolean stale;
124  | 
125  |     known = stale = clear_stale_map(GOLD_CLASS);
126  | 
127  |     /* look for gold carried by monsters (might be in a container) */
128  |     for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
129  |     	if (DEADMONSTER(mtmp)) continue;	/* probably not needed in this case but... */
130  | 	if (mtmp->mgold || monsndx(mtmp->data) == PM_GOLD_GOLEM) {
131  | 	    known = TRUE;
132  | 	    goto outgoldmap;	/* skip further searching */
133  | 	} else for (obj = mtmp->minvent; obj; obj = obj->nobj)
134  | 	    if (o_in(obj, GOLD_CLASS)) {
135  | 		known = TRUE;
136  | 		goto outgoldmap;	/* skip further searching */
137  | 	    }
138  |     }
139  |     
140  |     /* look for gold objects */
141  |     for (obj = fobj; obj; obj = obj->nobj)
142  | 	if (o_in(obj, GOLD_CLASS)) {
143  | 	    known = TRUE;
144  | 	    if (obj->ox != u.ux || obj->oy != u.uy) goto outgoldmap;
145  | 	}
146  | 
147  |     if (!known) {
148  | 	/* no gold found */
149  | 	if (sobj) strange_feeling(sobj, "You feel materially poor.");
150  | 	return(1);
151  |     }
152  |     /* only under me - no separate display required */
153  |     if (stale) docrt();
154  |     You("notice some gold between your %s.", makeplural(body_part(FOOT)));
155  |     return(0);
156  | 
157  | outgoldmap:
158  |     cls();
159  | 
160  |     u.uinwater = 0;
161  |     /* Discover gold locations. */
162  |     for (obj = fobj; obj; obj = obj->nobj)
163  | 	if ((temp = o_in(obj, GOLD_CLASS))) {
164  | 	    if (temp != obj) {
165  | 		temp->ox = obj->ox;
166  | 		temp->oy = obj->oy;
167  | 	    }
168  | 	    map_object(temp,1);
169  | 	}
170  |     for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
171  |     	if (DEADMONSTER(mtmp)) continue;	/* probably overkill here */
172  | 	if (mtmp->mgold || monsndx(mtmp->data) == PM_GOLD_GOLEM) {
173  | 	    struct obj gold;
174  | 
175  | 	    gold.otyp = GOLD_PIECE;
176  | 	    gold.ox = mtmp->mx;
177  | 	    gold.oy = mtmp->my;
178  | 	    map_object(&gold,1);
179  | 	} else for (obj = mtmp->minvent; obj; obj = obj->nobj)
180  | 	    if ((temp = o_in(obj, GOLD_CLASS))) {
181  | 		temp->ox = mtmp->mx;
182  | 		temp->oy = mtmp->my;
183  | 		map_object(temp,1);
184  | 		break;
185  | 	    }
186  |     }
187  |     
188  |     newsym(u.ux,u.uy);
189  |     You_feel("very greedy, and sense gold!");
190  |     exercise(A_WIS, TRUE);
191  |     display_nhwindow(WIN_MAP, TRUE);
192  |     docrt();
193  |     u.uinwater = uw;
194  |     if (Underwater) under_water(2);
195  |     if (u.uburied) under_ground(2);
196  |     return(0);
197  | }
198  | 
199  | /* returns 1 if nothing was detected		*/
200  | /* returns 0 if something was detected		*/
201  | int
202  | food_detect(sobj)
203  | register struct obj	*sobj;
204  | {
205  |     register struct obj *obj;
206  |     register struct monst *mtmp;
207  |     register int ct = 0, ctu = 0;
208  |     boolean confused = (Confusion || (sobj && sobj->cursed)), stale;
209  |     char oclass = confused ? POTION_CLASS : FOOD_CLASS;
210  |     const char *what = confused ? something : "food";
211  |     int uw = u.uinwater;
212  | 
213  |     stale = clear_stale_map(oclass);
214  | 
215  |     for (obj = fobj; obj; obj = obj->nobj)
216  | 	if (o_in(obj, oclass)) {
217  | 	    if (obj->ox == u.ux && obj->oy == u.uy) ctu++;
218  | 	    else ct++;
219  | 	}
220  |     for (mtmp = fmon; mtmp && !ct; mtmp = mtmp->nmon) {
221  | 	/* no DEADMONSTER(mtmp) check needed since dmons never have inventory */
222  | 	for (obj = mtmp->minvent; obj; obj = obj->nobj)
223  | 	    if (o_in(obj, oclass)) {
224  | 		ct++;
225  | 		break;
226  | 	    }
227  |     }
228  |     
229  |     if (!ct && !ctu) {
230  | 	known = stale && !confused;
231  | 	if (stale) {
232  | 	    docrt();
233  | 	    You("sense a lack of %s nearby.", what);
234  | 	} else if (sobj)
235  | 	    strange_feeling(sobj, "Your nose twitches.");
236  | 	return !stale;
237  |     } else if (!ct) {
238  | 	known = TRUE;
239  | 	You("%s %s nearby.", sobj ? "smell" : "sense", what);
240  |     } else {
241  | 	struct obj *temp;
242  | 	known = TRUE;
243  | 	cls();
244  | 	u.uinwater = 0;
245  | 	for (obj = fobj; obj; obj = obj->nobj)
246  | 	    if ((temp = o_in(obj, oclass)) != 0) {
247  | 		if (temp != obj) {
248  | 		    temp->ox = obj->ox;
249  | 		    temp->oy = obj->oy;
250  | 		}
251  | 		map_object(temp,1);
252  | 	    }
253  | 	for (mtmp = fmon; mtmp; mtmp = mtmp->nmon)
254  | 	    /* no DEADMONSTER(mtmp) check needed since dmons never have inventory */
255  | 	    for (obj = mtmp->minvent; obj; obj = obj->nobj)
256  | 		if ((temp = o_in(obj, oclass)) != 0) {
257  | 		    temp->ox = mtmp->mx;
258  | 		    temp->oy = mtmp->my;
259  | 		    map_object(temp,1);
260  | 		    break;	/* skip rest of this monster's inventory */
261  | 		}
262  | 	newsym(u.ux,u.uy);
263  | 	if (sobj) Your("nose tingles and you smell %s.", what);
264  | 	else You("sense %s.", what);
265  | 	display_nhwindow(WIN_MAP, TRUE);
266  | 	exercise(A_WIS, TRUE);
267  | 	docrt();
268  | 	u.uinwater = uw;
269  | 	if (Underwater) under_water(2);
270  | 	if (u.uburied) under_ground(2);
271  |     }
272  |     return(0);
273  | }
274  | 
275  | /*
276  |  * Used for scrolls, potions, and crystal balls.  Returns:
277  |  *
278  |  *	1 - nothing was detected
279  |  *	0 - something was detected
280  |  */
281  | int
282  | object_detect(detector, class)
283  | struct obj	*detector;	/* object doing the detecting */
284  | int		class;		/* an object class, 0 for all */
285  | {
286  |     register int x, y;
287  |     int is_cursed = (detector && detector->cursed);
288  |     int do_dknown =
289  | 	(detector && detector->oclass == POTION_CLASS && detector->blessed);
290  |     int ct = 0, ctu = 0;
291  |     register struct obj *obj, *otmp = (struct obj *)0;
292  |     register struct monst *mtmp;
293  |     int uw = u.uinwater;
294  |     const char *stuff;
295  | 
296  |     if (class < 0 || class >= MAXOCLASSES) {
297  | 	impossible("object_detect:  illegal class %d", class);
298  | 	class = 0;
299  |     }
300  | 
301  |     if (Hallucination || (Confusion && class == SCROLL_CLASS))
302  | 	stuff = something;
303  |     else
304  | 	stuff = class ? oclass_names[class] : "objects";
305  | 
306  |     if (do_dknown) for(obj = invent; obj; obj = obj->nobj) do_dknown_of(obj);
307  | 
308  |     for (obj = fobj; obj; obj = obj->nobj) {
309  | 	if (!class || o_in(obj, class)) {
310  | 	    if (obj->ox == u.ux && obj->oy == u.uy) ctu++;
311  | 	    else ct++;
312  | 	}
313  | 	if (do_dknown) do_dknown_of(obj);
314  |     }
315  | 
316  |     for (obj = level.buriedobjlist; obj; obj = obj->nobj) {
317  | 	if (!class || o_in(obj, class)) {
318  | 	    if (obj->ox == u.ux && obj->oy == u.uy) ctu++;
319  | 	    else ct++;
320  | 	}
321  | 	if (do_dknown) do_dknown_of(obj);
322  |     }
323  | 
324  |     for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
325  | 	if (DEADMONSTER(mtmp)) continue;
326  | 	for (obj = mtmp->minvent; obj; obj = obj->nobj) {
327  | 	    if (!class || o_in(obj, class)) ct++;
328  | 	    if (do_dknown) do_dknown_of(obj);
329  | 	}
330  | 	if ((is_cursed && mtmp->m_ap_type == M_AP_OBJECT &&
331  | 	    (!class || class == objects[mtmp->mappearance].oc_class)) ||
332  | 	    (mtmp->mgold && (!class || class == GOLD_CLASS))) {
333  | 	    ct++;
334  | 	    break;
335  | 	}
336  |     }
337  | 
338  |     if (!clear_stale_map(!class ? ALL_CLASSES : class) && !ct) {
339  | 	if (!ctu) {
340  | 	    if (detector)
341  | 		strange_feeling(detector, "You feel a lack of something.");
342  | 	    return 1;
343  | 	}
344  | 
345  | 	You("sense %s nearby.", stuff);
346  | 	return 0;
347  |     }
348  | 
349  |     cls();
350  | 
351  |     u.uinwater = 0;
352  | /*
353  |  *	Map all buried objects first.
354  |  */
355  |     for (obj = level.buriedobjlist; obj; obj = obj->nobj)
356  | 	if (!class || (otmp = o_in(obj, class))) {
357  | 	    if (class) {
358  | 		if (otmp != obj) {
359  | 		    otmp->ox = obj->ox;
360  | 		    otmp->oy = obj->oy;
361  | 		}
362  | 		map_object(otmp, 1);
363  | 	    } else
364  | 		map_object(obj, 1);
365  | 	}
366  |     /*
367  |      * If we are mapping all objects, map only the top object of a pile or
368  |      * the first object in a monster's inventory.  Otherwise, go looking
369  |      * for a matching object class and display the first one encountered
370  |      * at each location.
371  |      *
372  |      * Objects on the floor override buried objects.
373  |      */
374  |     for (x = 1; x < COLNO; x++)
375  | 	for (y = 0; y < ROWNO; y++)
376  | 	    for (obj = level.objects[x][y]; obj; obj = obj->nexthere)
377  | 		if (!class || (otmp = o_in(obj, class))) {
378  | 		    if (class) {
379  | 			if (otmp != obj) {
380  | 			    otmp->ox = obj->ox;
381  | 			    otmp->oy = obj->oy;
382  | 			}
383  | 			map_object(otmp, 1);
384  | 		    } else
385  | 			map_object(obj, 1);
386  | 		    break;
387  | 		}
388  | 
389  |     /* Objects in the monster's inventory override floor objects. */
390  |     for (mtmp = fmon ; mtmp ; mtmp = mtmp->nmon) {
391  | 	if (DEADMONSTER(mtmp)) continue;
392  | 	for (obj = mtmp->minvent; obj; obj = obj->nobj)
393  | 	    if (!class || (otmp = o_in(obj, class))) {
394  | 		if (!class) otmp = obj;
395  | 		otmp->ox = mtmp->mx;		/* at monster location */
396  | 		otmp->oy = mtmp->my;
397  | 		map_object(otmp, 1);
398  | 		break;
399  | 	    }
400  | 	/* Allow a mimic to override the detected objects it is carrying. */
401  | 	if (is_cursed && mtmp->m_ap_type == M_AP_OBJECT &&
402  | 		(!class || class == objects[mtmp->mappearance].oc_class)) {
403  | 	    struct obj temp;
404  | 
405  | 	    temp.otyp = mtmp->mappearance;	/* needed for obj_to_glyph() */
406  | 	    temp.ox = mtmp->mx;
407  | 	    temp.oy = mtmp->my;
408  | 	    temp.corpsenm = PM_TENGU;		/* if mimicing a corpse */
409  | 	    map_object(&temp, 1);
410  | 	} else if (mtmp->mgold && (!class || class == GOLD_CLASS)) {
411  | 	    struct obj gold;
412  | 
413  | 	    gold.otyp = GOLD_PIECE;
414  | 	    gold.ox = mtmp->mx;
415  | 	    gold.oy = mtmp->my;
416  | 	    map_object(&gold, 1);
417  | 	}
418  |     }
419  | 
420  |     newsym(u.ux,u.uy);
421  |     You("detect the %s of %s.", ct ? "presence" : "absence", stuff);
422  |     display_nhwindow(WIN_MAP, TRUE);
423  |     /*
424  |      * What are we going to do when the hero does an object detect while blind
425  |      * and the detected object covers a known pool?
426  |      */
427  |     docrt();	/* this will correctly reset vision */
428  | 
429  |     u.uinwater = uw;
430  |     if (Underwater) under_water(2);
431  |     if (u.uburied) under_ground(2);
432  |     return 0;
433  | }
434  | 
435  | /*
436  |  * Used by: crystal balls, potions, fountains
437  |  *
438  |  * Returns 1 if nothing was detected.
439  |  * Returns 0 if something was detected.
440  |  */
441  | int
442  | monster_detect(otmp, mclass)
443  | register struct obj *otmp;	/* detecting object (if any) */
444  | int mclass;			/* monster class, 0 for all */
445  | {
446  |     register struct monst *mtmp;
447  |     int mcnt = 0;
448  | 
449  | 
450  |     /* Note: This used to just check fmon for a non-zero value
451  |      * but in versions since 3.3.0 fmon can test TRUE due to the
452  |      * presence of dmons, so we have to find at least one
453  |      * with positive hit-points to know for sure.
454  |      */
455  |     for (mtmp = fmon; mtmp; mtmp = mtmp->nmon)
456  |     	if (!DEADMONSTER(mtmp)) {
457  | 		mcnt++;
458  | 		break;
459  | 	}
460  | 
461  |     if (!mcnt) {
462  | 	if (otmp)
463  | 	    strange_feeling(otmp, Hallucination ?
464  | 			    "You get the heebie jeebies." :
465  | 			    "You feel threatened.");
466  | 	return 1;
467  |     } else {
468  | 	boolean woken = FALSE;
469  | 
470  | 	cls();
471  | 	for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
472  | 	    if (DEADMONSTER(mtmp)) continue;
473  | 	    if (!mclass || mtmp->data->mlet == mclass)
474  | 	    if (mtmp->mx > 0)
475  | 		show_glyph(mtmp->mx,mtmp->my,mon_to_glyph(mtmp));
476  | 	    if (otmp && otmp->cursed &&
477  | 		(mtmp->msleeping || !mtmp->mcanmove)) {
478  | 		mtmp->msleeping = mtmp->mfrozen = 0;
479  | 		mtmp->mcanmove = 1;
480  | 		woken = TRUE;
481  | 	    }
482  | 	}
483  | 	display_self();
484  | 	You("sense the presence of monsters.");
485  | 	if (woken)
486  | 	    pline("Monsters sense the presence of you.");
487  | 	display_nhwindow(WIN_MAP, TRUE);
488  | 	docrt();
489  | 	if (Underwater) under_water(2);
490  | 	if (u.uburied) under_ground(2);
491  |     }
492  |     return 0;
493  | }
494  | 
495  | STATIC_OVL void
496  | sense_trap(trap, x, y, src_cursed)
497  | struct trap *trap;
498  | xchar x, y;
499  | int src_cursed;
500  | {
501  |     if (Hallucination || src_cursed) {
502  | 	struct obj obj;			/* fake object */
503  | 	if (trap) {
504  | 	    obj.ox = trap->tx;
505  | 	    obj.oy = trap->ty;
506  | 	} else {
507  | 	    obj.ox = x;
508  | 	    obj.oy = y;
509  | 	}
510  | 	obj.otyp = (src_cursed) ? GOLD_PIECE : random_object();
511  | 	obj.corpsenm = random_monster();	/* if otyp == CORPSE */
512  | 	map_object(&obj,1);
513  |     } else if (trap) {
514  | 	map_trap(trap,1);
515  | 	trap->tseen = 1;
516  |     } else {
517  | 	struct trap temp_trap;		/* fake trap */
518  | 	temp_trap.tx = x;
519  | 	temp_trap.ty = y;
520  | 	temp_trap.ttyp = BEAR_TRAP;	/* some kind of trap */
521  | 	map_trap(&temp_trap,1);
522  |     }
523  | 
524  | }
525  | 
526  | /* the detections are pulled out so they can	*/
527  | /* also be used in the crystal ball routine	*/
528  | /* returns 1 if nothing was detected		*/
529  | /* returns 0 if something was detected		*/
530  | int
531  | trap_detect(sobj)
532  | register struct obj *sobj;
533  | /* sobj is null if crystal ball, *scroll if gold detection scroll */
534  | {
535  |     register struct trap *ttmp;
536  |     register struct obj *obj;
537  |     register int door;
538  |     int uw = u.uinwater;
539  |     boolean found = FALSE;
540  |     coord cc;
541  | 
542  |     for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap) {
543  | 	if (ttmp->tx != u.ux || ttmp->ty != u.uy)
544  | 	    goto outtrapmap;
545  | 	else found = TRUE;
546  |     }
547  |     for (obj = fobj; obj; obj = obj->nobj) {
548  | 	if ((obj->otyp==LARGE_BOX || obj->otyp==CHEST) && obj->otrapped) {
549  | 	    if (obj->ox != u.ux || obj->oy != u.uy)
550  | 		goto outtrapmap;
551  | 	    else found = TRUE;
552  | 	}
553  |     }
554  |     for (door = 0; door <= doorindex; door++) {
555  | 	cc = doors[door];
556  | 	if (levl[cc.x][cc.y].doormask & D_TRAPPED) {
557  | 	    if (cc.x != u.ux || cc.x != u.uy)
558  | 		goto outtrapmap;
559  | 	    else found = TRUE;
560  | 	}
561  |     }
562  |     if (!found) {
563  | 	char buf[42];
564  | 	Sprintf(buf, "Your %s stop itching.", makeplural(body_part(TOE)));
565  | 	strange_feeling(sobj,buf);
566  | 	return(1);
567  |     }
568  |     /* traps exist, but only under me - no separate display required */
569  |     Your("%s itch.", makeplural(body_part(TOE)));
570  |     return(0);
571  | outtrapmap:
572  |     cls();
573  | 
574  |     u.uinwater = 0;
575  |     for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap)
576  | 	sense_trap(ttmp, 0, 0, sobj && sobj->cursed);
577  | 
578  |     for (obj = fobj; obj; obj = obj->nobj)
579  | 	if ((obj->otyp==LARGE_BOX || obj->otyp==CHEST) && obj->otrapped)
580  | 	sense_trap((struct trap *)0, obj->ox, obj->oy, sobj && sobj->cursed);
581  | 
582  |     for (door = 0; door <= doorindex; door++) {
583  | 	cc = doors[door];
584  | 	if (levl[cc.x][cc.y].doormask & D_TRAPPED)
585  | 	sense_trap((struct trap *)0, cc.x, cc.y, sobj && sobj->cursed);
586  |     }
587  | 
588  |     newsym(u.ux,u.uy);
589  |     You_feel("%s.", sobj && sobj->cursed ? "very greedy" : "entrapped");
590  |     display_nhwindow(WIN_MAP, TRUE);
591  |     docrt();
592  |     u.uinwater = uw;
593  |     if (Underwater) under_water(2);
594  |     if (u.uburied) under_ground(2);
595  |     return(0);
596  | }
597  | 
598  | const char *
599  | level_distance(where)
600  | d_level *where;
601  | {
602  |     register schar ll = depth(&u.uz) - depth(where);
603  |     register boolean indun = (u.uz.dnum == where->dnum);
604  | 
605  |     if (ll < 0) {
606  | 	if (ll < (-8 - rn2(3)))
607  | 	    if (!indun)	return "far away";
608  | 	    else	return "far below";
609  | 	else if (ll < -1)
610  | 	    if (!indun)	return "away below you";
611  | 	    else	return "below you";
612  | 	else
613  | 	    if (!indun)	return "in the distance";
614  | 	    else	return "just below";
615  |     } else if (ll > 0) {
616  | 	if (ll > (8 + rn2(3)))
617  | 	    if (!indun)	return "far away";
618  | 	    else	return "far above";
619  | 	else if (ll > 1)
620  | 	    if (!indun)	return "away above you";
621  | 	    else	return "above you";
622  | 	else
623  | 	    if (!indun)	return "in the distance";
624  | 	    else	return "just above";
625  |     } else
626  | 	    if (!indun)	return "in the distance";
627  | 	    else	return "near you";
628  | }
629  | 
630  | static struct {
631  |     const char *what;
632  |     d_level *where;
633  | } level_detects[] = {
634  |   { "Delphi", &oracle_level },
635  |   { "Medusa's lair", &medusa_level },
636  |   { "a castle", &stronghold_level },
637  |   { "the Wizard of Yendor's tower", &wiz1_level },
638  | };
639  | 
640  | void
641  | use_crystal_ball(obj)
642  | struct obj *obj;
643  | {
644  |     char ch;
645  |     int oops;
646  |     const char *bname = xname(obj);
647  | 
648  |     if (Blind) {
649  | 	pline("Too bad you can't see %s", the(bname));
650  | 	return;
651  |     }
652  |     oops = (rnd(20) > ACURR(A_INT) || obj->cursed);
653  |     if (oops && (obj->spe > 0)) {
654  | 	switch (rnd(obj->oartifact ? 4 : 5)) {
655  | 	case 1 : pline("%s is too much to comprehend!", The(bname));
656  | 	    break;
657  | 	case 2 : pline("%s confuses you!", The(bname));
658  | 	    make_confused(HConfusion + rnd(100),FALSE);
659  | 	    break;
660  | 	case 3 : if (!resists_blnd(&youmonst)) {
661  | 		pline("%s damages your vision!", The(bname));
662  | 		make_blinded(Blinded + rnd(100),FALSE);
663  | 	    } else {
664  | 		pline("%s assaults your vision.", The(bname));
665  | 		You("are unaffected!");
666  | 	    }
667  | 	    break;
668  | 	case 4 : pline("%s zaps your mind!", The(bname));
669  | 	    make_hallucinated(HHallucination + rnd(100),FALSE,0L);
670  | 	    break;
671  | 	case 5 : pline("%s explodes!", The(bname));
672  | 	    useup(obj);
673  | 	    losehp(rnd(30), "exploding crystal ball", KILLED_BY_AN);
674  | 	    break;
675  | 	}
676  | 	check_unpaid(obj);
677  | 	obj->spe--;
678  | 	return;
679  |     }
680  | 
681  |     if (Hallucination) {
682  | 	if (!obj->spe) {
683  | 	    pline("All you see is funky %s haze.", hcolor((char *)0));
684  | 	} else {
685  | 	    switch(rnd(6)) {
686  | 	    case 1 : You("grok some groovy globs of incandescent lava.");
687  | 		break;
688  | 	    case 2 : pline("Whoa!  Psychedelic colors, %s!",
689  | 			   poly_gender() == 1 ? "babe" : "dude");
690  | 		break;
691  | 	    case 3 : pline_The("crystal pulses with sinister %s light!",
692  | 				hcolor((char *)0));
693  | 		break;
694  | 	    case 4 : You("see goldfish swimming above fluorescent rocks.");
695  | 		break;
696  | 	    case 5 : You("see tiny snowflakes spinning around a miniature farmhouse.");
697  | 		break;
698  | 	    default: pline("Oh wow... like a kaleidoscope!");
699  | 		break;
700  | 	    }
701  | 	    check_unpaid(obj);
702  | 	    obj->spe--;
703  | 	}
704  | 	return;
705  |     }
706  | 
707  |     /* read a single character */
708  |     if (flags.verbose) You("may look for an object or monster symbol.");
709  |     ch = yn_function("What do you look for?", (char *)0, '\0');
710  |     if (index(quitchars,ch)) {
711  | 	if (flags.verbose) pline(Never_mind);
712  | 	return;
713  |     }
714  |     You("peer into %s...", the(bname));
715  |     nomul(-rnd(10));
716  |     nomovemsg = "";
717  |     if (obj->spe <= 0)
718  | 	pline_The("vision is unclear.");
719  |     else {
720  | 	int class;
721  | 	int ret = 0;
722  | 
723  | 	makeknown(CRYSTAL_BALL);
724  | 	check_unpaid(obj);
725  | 	obj->spe--;
726  | 
727  | 	if ((class = def_char_to_objclass(ch)) != MAXOCLASSES)
728  | 		ret = object_detect((struct obj *)0, class);
729  | 	else if ((class = def_char_to_monclass(ch)) != MAXMCLASSES)
730  | 		ret = monster_detect((struct obj *)0, class);
731  | 	else switch(ch) {
732  | 		case '^':
733  | 		    ret = trap_detect((struct obj *)0);
734  | 		    break;
735  | 		default:
736  | 		    {
737  | 		    int i = rn2(SIZE(level_detects));
738  | 		    You("see %s, %s.",
739  | 			level_detects[i].what,
740  | 			level_distance(level_detects[i].where));
741  | 		    }
742  | 		    ret = 0;
743  | 		    break;
744  | 	}
745  | 
746  | 	if (ret) {
747  | 	    if (!rn2(100))  /* make them nervous */
748  | 		You("see the Wizard of Yendor gazing out at you.");
749  | 	    else pline_The("vision is unclear.");
750  | 	}
751  |     }
752  |     return;
753  | }
754  | 
755  | STATIC_OVL void
756  | show_map_spot(x, y)
757  | register int x, y;
758  | {
759  |     register struct rm *lev;
760  | 
761  |     if (Confusion && rn2(7)) return;
762  |     lev = &levl[x][y];
763  | 
764  |     lev->seenv = SVALL;
765  | 
766  |     /* Secret corridors are found, but not secret doors. */
767  |     if (lev->typ == SCORR) {
768  | 	lev->typ = CORR;
769  | 	unblock_point(x,y);
770  |     }
771  | 
772  |     /* if we don't remember an object or trap there, map it */
773  |     if (lev->typ == ROOM ?
774  | 	    (glyph_is_cmap(lev->glyph) && !glyph_is_trap(lev->glyph) &&
775  | 		glyph_to_cmap(lev->glyph) != ROOM) :
776  | 	    (!glyph_is_object(lev->glyph) && !glyph_is_trap(lev->glyph))) {
777  | 	if (level.flags.hero_memory) {
778  | 	    magic_map_background(x,y,0);
779  | 	    newsym(x,y);			/* show it, if not blocked */
780  | 	} else {
781  | 	    magic_map_background(x,y,1);	/* display it */
782  | 	}
783  |     }
784  | }
785  | 
786  | void
787  | do_mapping()
788  | {
789  |     register int zx, zy;
790  |     int uw = u.uinwater;
791  | 
792  |     u.uinwater = 0;
793  |     for (zx = 1; zx < COLNO; zx++)
794  | 	for (zy = 0; zy < ROWNO; zy++)
795  | 	    show_map_spot(zx, zy);
796  |     exercise(A_WIS, TRUE);
797  |     u.uinwater = uw;
798  |     if (!level.flags.hero_memory || Underwater) {
799  | 	flush_screen(1);			/* flush temp screen */
800  | 	display_nhwindow(WIN_MAP, TRUE);	/* wait */
801  | 	docrt();
802  |     }
803  | }
804  | 
805  | void
806  | do_vicinity_map()
807  | {
808  |     register int zx, zy;
809  |     int lo_y = (u.uy-5 < 0 ? 0 : u.uy-5),
810  | 	hi_y = (u.uy+6 > ROWNO ? ROWNO : u.uy+6),
811  | 	lo_x = (u.ux-9 < 1 ? 1 : u.ux-9),	/* avoid column 0 */
812  | 	hi_x = (u.ux+10 > COLNO ? COLNO : u.ux+10);
813  | 
814  |     for (zx = lo_x; zx < hi_x; zx++)
815  | 	for (zy = lo_y; zy < hi_y; zy++)
816  | 	    show_map_spot(zx, zy);
817  | 
818  |     if (!level.flags.hero_memory || Underwater) {
819  | 	flush_screen(1);			/* flush temp screen */
820  | 	display_nhwindow(WIN_MAP, TRUE);	/* wait */
821  | 	docrt();
822  |     }
823  | }
824  | 
825  | /* convert a secret door into a normal door */
826  | void
827  | cvt_sdoor_to_door(lev)
828  | struct rm *lev;
829  | {
830  | 	int newmask = lev->doormask & ~WM_MASK;
831  | 
832  | #ifdef REINCARNATION
833  | 	if (Is_rogue_level(&u.uz))
834  | 	    /* rogue didn't have doors, only doorways */
835  | 	    newmask = D_NODOOR;
836  | 	else
837  | #endif
838  | 	    /* newly exposed door is closed */
839  | 	    if (!(newmask & D_LOCKED)) newmask |= D_CLOSED;
840  | 
841  | 	lev->typ = DOOR;
842  | 	lev->doormask = newmask;
843  | }
844  | 
845  | 
846  | STATIC_PTR void
847  | findone(zx,zy,num)
848  | int zx,zy;
849  | genericptr_t num;
850  | {
851  | 	register struct trap *ttmp;
852  | 	register struct monst *mtmp;
853  | 
854  | 	if(levl[zx][zy].typ == SDOOR) {
855  | 		cvt_sdoor_to_door(&levl[zx][zy]);	/* .typ = DOOR */
856  | 		newsym(zx, zy);
857  | 		(*(int*)num)++;
858  | 	} else if(levl[zx][zy].typ == SCORR) {
859  | 		levl[zx][zy].typ = CORR;
860  | 		newsym(zx, zy);
861  | 		(*(int*)num)++;
862  | 	} else if ((ttmp = t_at(zx, zy)) != 0) {
863  | 		if(!ttmp->tseen && ttmp->ttyp != STATUE_TRAP) {
864  | 			ttmp->tseen = 1;
865  | 			newsym(zx,zy);
866  | 			(*(int*)num)++;
867  | 		}
868  | 	} else if ((mtmp = m_at(zx, zy)) != 0) {
869  | 		if(mtmp->m_ap_type) {
870  | 			seemimic(mtmp);
871  | 			(*(int*)num)++;
872  | 		}
873  | 		if (mtmp->mundetected &&
874  | 		    (is_hider(mtmp->data) || mtmp->data->mlet == S_EEL)) {
875  | 			mtmp->mundetected = 0;
876  | 			newsym(zx, zy);
877  | 			(*(int*)num)++;
878  | 		}
879  | 		if (!canspotmon(mtmp) &&
880  | 				    !glyph_is_invisible(levl[zx][zy].glyph))
881  | 			map_invisible(zx, zy);
882  | 	} else if (glyph_is_invisible(levl[zx][zy].glyph)) {
883  | 		unmap_object(zx, zy);
884  | 		newsym(zx, zy);
885  | 		(*(int*)num)++;
886  | 	}
887  | }
888  | 
889  | STATIC_PTR void
890  | openone(zx,zy,num)
891  | int zx,zy;
892  | genericptr_t num;
893  | {
894  | 	register struct trap *ttmp;
895  | 	register struct obj *otmp;
896  | 
897  | 	if(OBJ_AT(zx, zy)) {
898  | 		for(otmp = level.objects[zx][zy];
899  | 				otmp; otmp = otmp->nexthere) {
900  | 		    if(Is_box(otmp) && otmp->olocked) {
901  | 			otmp->olocked = 0;
902  | 			(*(int*)num)++;
903  | 		    }
904  | 		}
905  | 		/* let it fall to the next cases. could be on trap. */
906  | 	}
907  | 	if(levl[zx][zy].typ == SDOOR || (levl[zx][zy].typ == DOOR &&
908  | 		      (levl[zx][zy].doormask & (D_CLOSED|D_LOCKED)))) {
909  | 		if(levl[zx][zy].typ == SDOOR)
910  | 		    cvt_sdoor_to_door(&levl[zx][zy]);	/* .typ = DOOR */
911  | 		if(levl[zx][zy].doormask & D_TRAPPED) {
912  | 		    if(distu(zx, zy) < 3) b_trapped("door", 0);
913  | 		    else Norep("You %s an explosion!",
914  | 				cansee(zx, zy) ? "see" :
915  | 				   (flags.soundok ? "hear" :
916  | 						"feel the shock of"));
917  | 		    wake_nearto(zx, zy, 11*11);
918  | 		    levl[zx][zy].doormask = D_NODOOR;
919  | 		} else
920  | 		    levl[zx][zy].doormask = D_ISOPEN;
921  | 		newsym(zx, zy);
922  | 		(*(int*)num)++;
923  | 	} else if(levl[zx][zy].typ == SCORR) {
924  | 		levl[zx][zy].typ = CORR;
925  | 		newsym(zx, zy);
926  | 		(*(int*)num)++;
927  | 	} else if ((ttmp = t_at(zx, zy)) != 0) {
928  | 		if (!ttmp->tseen && ttmp->ttyp != STATUE_TRAP) {
929  | 		    ttmp->tseen = 1;
930  | 		    newsym(zx,zy);
931  | 		    (*(int*)num)++;
932  | 		}
933  | 	} else if (find_drawbridge(&zx, &zy)) {
934  | 		/* make sure it isn't an open drawbridge */
935  | 		open_drawbridge(zx, zy);
936  | 		(*(int*)num)++;
937  | 	}
938  | }
939  | 
940  | int
941  | findit()	/* returns number of things found */
942  | {
943  | 	int num = 0;
944  | 
945  | 	if(u.uswallow) return(0);
946  | 	do_clear_area(u.ux, u.uy, BOLT_LIM, findone, (genericptr_t) &num);
947  | 	return(num);
948  | }
949  | 
950  | int
951  | openit()	/* returns number of things found and opened */
952  | {
953  | 	int num = 0;
954  | 
955  | 	if(u.uswallow) {
956  | 		if (is_animal(u.ustuck->data)) {
957  | 			if (Blind) pline("Its mouth opens!");
958  | 			else pline("%s opens its mouth!", Monnam(u.ustuck));
959  | 		}
960  | 		expels(u.ustuck, u.ustuck->data, TRUE);
961  | 		return(-1);
962  | 	}
963  | 
964  | 	do_clear_area(u.ux, u.uy, BOLT_LIM, openone, (genericptr_t) &num);
965  | 	return(num);
966  | }
967  | 
968  | void
969  | find_trap(trap)
970  | struct trap *trap;
971  | {
972  |     int tt = what_trap(trap->ttyp);
973  | 
974  |     You("find %s.", an(defsyms[trap_to_defsym(tt)].explanation));
975  |     trap->tseen = 1;
976  |     exercise(A_WIS, TRUE);
977  |     if (Blind)
978  | 	feel_location(trap->tx, trap->ty);
979  |     else
980  | 	newsym(trap->tx, trap->ty);
981  | }
982  | 
983  | int
984  | dosearch0(aflag)
985  | register int aflag;
986  | {
987  | #ifdef GCC_BUG
988  | /* some versions of gcc seriously muck up nested loops. if you get strange
989  |    crashes while searching in a version compiled with gcc, try putting
990  |    #define GCC_BUG in *conf.h (or adding -DGCC_BUG to CFLAGS in the
991  |    makefile).
992  |  */
993  | 	volatile xchar x, y;
994  | #else
995  | 	register xchar x, y;
996  | #endif
997  | 	register struct trap *trap;
998  | 	register struct monst *mtmp;
999  | 
1000 | 	if(u.uswallow) {
1001 | 		if (!aflag)
1002 | 			pline("What are you looking for?  The exit?");
1003 | 	} else {
1004 | 	    int fund = (uwep && uwep->oartifact &&
1005 | 		    spec_ability(uwep, SPFX_SEARCH)) ?
1006 | 			((uwep->spe > 5) ? 5 : uwep->spe) : 0;
1007 | 	    for(x = u.ux-1; x < u.ux+2; x++)
1008 | 	      for(y = u.uy-1; y < u.uy+2; y++) {
1009 | 		if(!isok(x,y)) continue;
1010 | 		if(x != u.ux || y != u.uy) {
1011 | 		    if (Blind && !aflag) feel_location(x,y);
1012 | 		    if(levl[x][y].typ == SDOOR) {
1013 | 			if(rnl(7-fund)) continue;
1014 | 			cvt_sdoor_to_door(&levl[x][y]);	/* .typ = DOOR */
1015 | 			exercise(A_WIS, TRUE);
1016 | 			nomul(0);
1017 | 			if (Blind && !aflag)
1018 | 			    feel_location(x,y);	/* make sure it shows up */
1019 | 			else
1020 | 			    newsym(x,y);
1021 | 		    } else if(levl[x][y].typ == SCORR) {
1022 | 			if(rnl(7-fund)) continue;
1023 | 			levl[x][y].typ = CORR;
1024 | 			unblock_point(x,y);	/* vision */
1025 | 			exercise(A_WIS, TRUE);
1026 | 			nomul(0);
1027 | 			newsym(x,y);
1028 | 		    } else {
1029 | 		/* Be careful not to find anything in an SCORR or SDOOR */
1030 | 			if((mtmp = m_at(x, y)) && !aflag) {
1031 | 			    if(mtmp->m_ap_type) {
1032 | 				seemimic(mtmp);
1033 | 		find:		exercise(A_WIS, TRUE);
1034 | 				if (!canspotmon(mtmp)) {
1035 | 				    if (glyph_is_invisible(levl[x][y].glyph)) {
1036 | 					/* found invisible monster in a square
1037 | 					 * which already has an 'I' in it.
1038 | 					 * Logically, this should still take
1039 | 					 * time and lead to a return(1), but if
1040 | 					 * we did that the player would keep
1041 | 					 * finding the same monster every turn.
1042 | 					 */
1043 | 					continue;
1044 | 				    } else {
1045 | 					You_feel("an unseen monster!");
1046 | 					map_invisible(x, y);
1047 | 				    }
1048 | 				} else
1049 | 				    You("find %s.", a_monnam(mtmp));
1050 | 				return(1);
1051 | 			    }
1052 | 			    if(mtmp->mundetected &&
1053 | 			(is_hider(mtmp->data) || mtmp->data->mlet == S_EEL)) {
1054 | 				mtmp->mundetected = 0;
1055 | 				newsym(x,y);
1056 | 				goto find;
1057 | 			    }
1058 | 			    if (!canspotmon(mtmp))
1059 | 				goto find;
1060 | 			}
1061 | 
1062 | 			/* see if an invisible monster has moved--if Blind,
1063 | 			 * feel_location() already did it
1064 | 			 */
1065 | 			if (!aflag && !mtmp && !Blind &&
1066 | 				    glyph_is_invisible(levl[x][y].glyph)) {
1067 | 			    unmap_object(x,y);
1068 | 			    newsym(x,y);
1069 | 			}
1070 | 
1071 | 			if ((trap = t_at(x,y)) && !trap->tseen && !rnl(8)) {
1072 | 			    nomul(0);
1073 | 
1074 | 			    if (trap->ttyp == STATUE_TRAP) {
1075 | 				if (activate_statue_trap(trap, x, y, FALSE))
1076 | 				    exercise(A_WIS, TRUE);
1077 | 				return(1);
1078 | 			    } else {
1079 | 				find_trap(trap);
1080 | 			    }
1081 | 			}
1082 | 		    }
1083 | 		}
1084 | 	    }
1085 | 	}
1086 | 	return(1);
1087 | }
1088 | 
1089 | int
1090 | dosearch()
1091 | {
1092 | 	return(dosearch0(0));
1093 | }
1094 | 
1095 | /* Pre-map the sokoban levels */
1096 | void
1097 | sokoban_detect()
1098 | {
1099 | 	register int x, y;
1100 | 	register struct trap *ttmp;
1101 | 	register struct obj *obj;
1102 | 
1103 | 	/* Map the background and boulders */
1104 | 	for (x = 1; x < COLNO; x++)
1105 | 	    for (y = 0; y < ROWNO; y++) {
1106 | 	    	levl[x][y].seenv = SVALL;
1107 | 	    	levl[x][y].waslit = TRUE;
1108 | 	    	map_background(x, y, 1);
1109 | 	    	for (obj = level.objects[x][y]; obj; obj = obj->nexthere)
1110 | 	    	    if (obj->otyp == BOULDER)
1111 | 	    	    	map_object(obj, 1);
1112 | 	    }
1113 | 
1114 | 	/* Map the traps */
1115 | 	for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap) {
1116 | 	    ttmp->tseen = 1;
1117 | 	    map_trap(ttmp, 1);
1118 | 	}
1119 | }
1120 | 
1121 | 
1122 | /*detect.c*/