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