1 /* SCCS Id: @(#)mail.c 3.4 2002/01/13 */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed. See license for details. */
13 * Notify user when new mail has arrived. Idea by Merlyn Leroy.
15 * The mail daemon can move with less than usual restraint. It can:
16 * - move diagonally from a door
17 * - use secret and closed doors
18 * - run through a monster ("Gangway!", etc.)
19 * - run over pools & traps
21 * Possible extensions:
22 * - Open the file MAIL and do fstat instead of stat for efficiency.
23 * (But sh uses stat, so this cannot be too bad.)
24 * - Examine the mail and produce a scroll of mail named "From somebody".
25 * - Invoke MAILREADER in such a way that only this single letter is read.
26 * - Do something to the text when the scroll is enchanted or cancelled.
27 * - Make the daemon always appear at a stairwell, and have it find a
30 * Note by Olaf Seibert: On the Amiga, we usually don't get mail. So we go
31 * through most of the effects at 'random' moments.
32 * Note by Paul Winner: The MSDOS port also 'fakes' the mail daemon at
36 STATIC_DCL boolean
md_start(coord
*);
37 STATIC_DCL boolean
md_stop(coord
*, coord
*);
38 STATIC_DCL boolean
md_rush(struct monst
*,int,int);
39 STATIC_DCL
void newmail(struct mail_info
*);
43 extern char *viz_rmin
, *viz_rmax
; /* line-of-sight limits (vision.c) */
47 # if !defined(UNIX) && !defined(VMS) && !defined(LAN_MAIL)
57 /* DON'T trust all Unices to declare getpwuid() in <pwd.h> */
58 # if !defined(_BULL_SOURCE) && !defined(__sgi) && !defined(_M_UNIX)
59 # if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__))
61 /* DO trust all SVR4 to typedef uid_t in <sys/types.h> (probably to a long) */
62 # if defined(POSIX_TYPES) || defined(SVR4) || defined(HPUX)
63 extern struct passwd
*getpwuid(uid_t
);
65 extern struct passwd
*getpwuid(int);
70 static struct stat omstat
,nmstat
;
71 static char *mailbox
= (char *)0;
72 static long laststattime
;
74 # if !defined(MAILPATH) && defined(AMS) /* Just a placeholder for AMS */
75 # define MAILPATH "/dev/null"
77 # if !defined(MAILPATH) && (defined(LINUX) || defined(__osf__))
78 # define MAILPATH "/var/spool/mail/"
80 # if !defined(MAILPATH) && defined(__FreeBSD__)
81 # define MAILPATH "/var/mail/"
83 # if !defined(MAILPATH) && (defined(BSD) || defined(ULTRIX))
84 # define MAILPATH "/usr/spool/mail/"
86 # if !defined(MAILPATH) && (defined(SYSV) || defined(HPUX))
87 # define MAILPATH "/usr/mail/"
93 if(!mailbox
&& !(mailbox
= nh_getenv("MAIL"))) {
94 # if defined(MAILPATH) && !defined(PUBLIC_SERVER)
96 struct passwd ppasswd
;
98 (void) memcpy(&ppasswd
, getpwuid(getuid()), sizeof(struct passwd
));
100 mailbox
= (char *) alloc((unsigned) strlen(ppasswd
.pw_dir
)+sizeof(AMS_MAILBOX
));
101 strcpy(mailbox
, ppasswd
.pw_dir
);
102 strcat(mailbox
, AMS_MAILBOX
);
106 const char *pw_name
= getpwuid(getuid())->pw_name
;
107 mailbox
= (char *) alloc(sizeof(MAILPATH
)+strlen(pw_name
));
108 strcpy(mailbox
, MAILPATH
);
109 strcat(mailbox
, pw_name
);
115 if(stat(mailbox
, &omstat
)){
116 # ifdef PERMANENT_MAILBOX
117 pline("Cannot get status of MAIL=\"%s\".", mailbox
);
130 * Pick coordinates for a starting position for the mail daemon. Called
131 * from newmail() and newphone().
137 coord testcc
; /* scratch coordinates */
138 int row
; /* current row we are checking */
139 int lax
; /* if TRUE, pick a position in sight. */
140 int dd
; /* distance to current point */
141 int max_distance
; /* max distance found so far */
144 * If blind and not telepathic, then it doesn't matter what we pick ---
145 * the hero is not going to see it anyway. So pick a nearby position.
147 if (Blind
&& !Blind_telepat
) {
148 if (!enexto(startp
, u
.ux
, u
.uy
, (struct permonst
*) 0))
149 return FALSE
; /* no good posiitons */
154 * Arrive at an up or down stairwell if it is in line of sight from the
157 if (couldsee(upstair
.sx
, upstair
.sy
)) {
158 startp
->x
= upstair
.sx
;
159 startp
->y
= upstair
.sy
;
162 if (couldsee(dnstair
.sx
, dnstair
.sy
)) {
163 startp
->x
= dnstair
.sx
;
164 startp
->y
= dnstair
.sy
;
169 * Try to pick a location out of sight next to the farthest position away
170 * from the hero. If this fails, try again, just picking the farthest
171 * position that could be seen. What we really ought to be doing is
172 * finding a path from a stairwell...
174 * The arrays viz_rmin[] and viz_rmax[] are set even when blind. These
175 * are the LOS limits for each row.
177 lax
= 0; /* be picky */
180 for (row
= 0; row
< ROWNO
; row
++) {
181 if (viz_rmin
[row
] < viz_rmax
[row
]) {
182 /* There are valid positions on this row. */
183 dd
= distu(viz_rmin
[row
],row
);
184 if (dd
> max_distance
) {
188 startp
->x
= viz_rmin
[row
];
190 } else if (enexto(&testcc
, (xchar
)viz_rmin
[row
], row
,
191 (struct permonst
*) 0) &&
192 !cansee(testcc
.x
, testcc
.y
) &&
193 couldsee(testcc
.x
, testcc
.y
)) {
198 dd
= distu(viz_rmax
[row
],row
);
199 if (dd
> max_distance
) {
203 startp
->x
= viz_rmax
[row
];
205 } else if (enexto(&testcc
, (xchar
)viz_rmax
[row
], row
,
206 (struct permonst
*) 0) &&
207 !cansee(testcc
.x
,testcc
.y
) &&
208 couldsee(testcc
.x
, testcc
.y
)) {
217 if (max_distance
< 0) {
219 lax
= 1; /* just find a position */
229 * Try to choose a stopping point as near as possible to the starting
230 * position while still adjacent to the hero. If all else fails, try
231 * enexto(). Use enexto() as a last resort because enexto() chooses
232 * its point randomly, which is not what we want.
235 md_stop(stopp
, startp
)
236 coord
*stopp
; /* stopping position (we fill it in) */
237 coord
*startp
; /* starting positon (read only) */
239 int x
, y
, distance
, min_distance
= -1;
241 for (x
= u
.ux
-1; x
<= u
.ux
+1; x
++)
242 for (y
= u
.uy
-1; y
<= u
.uy
+1; y
++) {
243 if (!isok(x
, y
) || (x
== u
.ux
&& y
== u
.uy
)) continue;
245 if (ACCESSIBLE(levl
[x
][y
].typ
) && !MON_AT(x
,y
)) {
246 distance
= dist2(x
,y
,startp
->x
,startp
->y
);
247 if (min_distance
< 0 || distance
< min_distance
||
248 (distance
== min_distance
&& rn2(2))) {
251 min_distance
= distance
;
256 /* If we didn't find a good spot, try enexto(). */
257 if (min_distance
< 0 &&
258 !enexto(stopp
, u
.ux
, u
.uy
, &mons
[PM_MAIL_DAEMON
]))
264 /* Let the mail daemon have a larger vocabulary. */
265 static NEARDATA
const char *mail_text
[] = {
270 #define md_exclamations() (mail_text[rn2(3)])
273 * Make the mail daemon run through the dungeon. The daemon will run over
274 * any monsters that are in its path, but will replace them later. Return
275 * FALSE if the md gets stuck in a position where there is a monster. Return
281 register int tx
, ty
; /* destination of mail daemon */
283 struct monst
*mon
; /* displaced monster */
284 register int dx
, dy
; /* direction counters */
285 int fx
= md
->mx
, fy
= md
->my
; /* current location */
286 int nfx
= fx
, nfy
= fy
, /* new location */
287 d1
, d2
; /* shortest distances */
290 * It is possible that the monster at (fx,fy) is not the md when:
291 * the md rushed the hero and failed, and is now starting back.
293 if (m_at(fx
, fy
) == md
) {
294 remove_monster(fx
, fy
); /* pick up from orig position */
299 * At the beginning and exit of this loop, md is not placed in the
303 /* Find a good location next to (fx,fy) closest to (tx,ty). */
304 d1
= dist2(fx
,fy
,tx
,ty
);
305 for (dx
= -1; dx
<= 1; dx
++) for(dy
= -1; dy
<= 1; dy
++)
306 if ((dx
|| dy
) && isok(fx
+dx
,fy
+dy
) && !IS_WATERTUNNEL(levl
[fx
+dx
][fy
+dy
].typ
) &&
307 !IS_STWALL(levl
[fx
+dx
][fy
+dy
].typ
)) {
308 d2
= dist2(fx
+dx
,fy
+dy
,tx
,ty
);
316 /* Break if the md couldn't find a new position. */
317 if (nfx
== fx
&& nfy
== fy
) break;
319 fx
= nfx
; /* this is our new position */
322 /* Break if the md reaches its destination. */
323 if (fx
== tx
&& fy
== ty
) break;
325 if ((mon
= m_at(fx
,fy
)) != 0) /* save monster at this position */
326 verbalize("%s", md_exclamations());
327 else if (fx
== u
.ux
&& fy
== u
.uy
)
328 verbalize("Excuse me.");
330 place_monster(md
,fx
,fy
); /* put md down */
331 newsym(fx
,fy
); /* see it */
332 flush_screen(0); /* make sure md shows up */
333 delay_output(); /* wait a little bit */
335 /* Remove md from the dungeon. Restore original mon, if necessary. */
337 if ((mon
->mx
!= fx
) || (mon
->my
!= fy
))
338 place_worm_seg(mon
, fx
, fy
);
340 place_monster(mon
, fx
, fy
);
342 remove_monster(fx
, fy
);
347 * Check for a monster at our stopping position (this is possible, but
348 * very unlikely). If one exists, then have the md leave in disgust.
350 if ((mon
= m_at(fx
, fy
)) != 0) {
351 place_monster(md
, fx
, fy
); /* display md with text below */
353 verbalize("This place's too crowded. I'm outta here.");
355 if ((mon
->mx
!= fx
) || (mon
->my
!= fy
)) /* put mon back */
356 place_worm_seg(mon
, fx
, fy
);
358 place_monster(mon
, fx
, fy
);
364 place_monster(md
, fx
, fy
); /* place at final spot */
367 delay_output(); /* wait a little bit */
372 /* Deliver a scroll of mail. */
376 struct mail_info
*info
;
380 boolean message_seen
= FALSE
;
382 /* Try to find good starting and stopping places. */
383 if (!md_start(&start
) || !md_stop(&stop
,&start
)) goto give_up
;
385 /* Make the daemon. Have it rush towards the hero. */
386 if (!(md
= makemon(&mons
[PM_MAIL_DAEMON
], start
.x
, start
.y
, NO_MM_FLAGS
)))
388 if (!md_rush(md
, stop
.x
, stop
.y
)) goto go_back
;
391 verbalize("%s, %s! %s.", Hello(md
), playeraliasname
, info
->display_txt
);
394 if (info
->message_typ
) {
395 struct obj
*obj
= mksobj(SCR_MAIL
, FALSE
, FALSE
, FALSE
);
398 /* In game master mode, mail from me will spawn monsters. Read it immediately, so you can't avoid them :P --Amy */
400 if (flags
.gmmode
|| flags
.supergmmode
) readmail(obj
);
402 if (distu(md
->mx
,md
->my
) > 2)
404 display_nhwindow(WIN_MESSAGE
, FALSE
);
405 if (info
->object_nam
) {
406 obj
= oname(obj
, info
->object_nam
);
407 if (info
->response_cmd
) { /*(hide extension of the obj name)*/
408 int namelth
= info
->response_cmd
- info
->object_nam
- 1;
409 if ( namelth
<= 0 || namelth
>= (int) obj
->onamelth
)
410 impossible("mail delivery screwed up");
412 *(ONAME(obj
) + namelth
) = '\0';
413 /* Note: renaming object will discard the hidden command. */
416 obj
= hold_another_object(obj
, "Oops!",
417 (const char *)0, (const char *)0);
420 /* zip back to starting location */
422 (void) md_rush(md
, start
.x
, start
.y
);
424 /* deliver some classes of messages even if no daemon ever shows up */
426 if (!message_seen
&& info
->message_typ
== MSG_OTHER
)
427 pline("Hark! \"%s.\"", info
->display_txt
);
430 # if !defined(UNIX) && !defined(VMS) && !defined(LAN_MAIL)
435 if (u
.uswallow
|| !flags
.biff
) return;
436 if (mustgetmail
< 0) {
437 #if defined(AMIGA) || defined(MSDOS) || defined(TOS) || defined(OS2)
438 mustgetmail
=(moves
<2000)?(100+rn2(2000)):(2000+rn2(3000));
442 if (--mustgetmail
<= 0) {
443 static struct mail_info
444 deliver
= {MSG_MAIL
,"I have some mail for you",0,0};
457 line
= getrumor(bcsign(otmp
), buf
, TRUE
);
459 line
= "NetHack rumors file closed for renovation.";
461 pline("Unfortunately you cannot see what it says.");
463 pline("It reads: \"%s\"", line
);
467 # endif /* !UNIX && !VMS */
474 if (iflags
.debug_fuzzer
) return;
478 mailckfreq
= (iflags
.simplemail
? 5 : 10);
483 if(!mailbox
|| u
.uswallow
|| !flags
.biff
484 || moves
< laststattime
+ mailckfreq
)
487 laststattime
= moves
;
488 if(stat(mailbox
, &nmstat
)){
489 # ifdef PERMANENT_MAILBOX
490 pline("Cannot get status of MAIL=\"%s\" anymore.", mailbox
);
495 } else if(nmstat
.st_mtime
> omstat
.st_mtime
) {
496 if (nmstat
.st_size
) {
497 static struct mail_info deliver
= {
498 # ifndef NO_MAILREADER
499 MSG_MAIL
, "I have some mail for you",
501 /* suppress creation and delivery of scroll of mail */
502 MSG_OTHER
, "You have some mail in the outside world",
508 getmailstatus(); /* might be too late ... */
517 #ifdef DEF_MAILREADER
518 register const char *mr
= 0;
519 #endif /* DEF_MAILREADER */
521 if (iflags
.simplemail
)
523 FILE* mb
= fopen(mailbox
, "r");
524 char curline
[102], *msg
;
525 boolean seen_one_already
= FALSE
;
526 struct flock fl
= { 0 };
529 fl
.l_whence
= SEEK_SET
;
536 /* Allow this call to block. */
537 if (fcntl (fileno (mb
), F_SETLKW
, &fl
) == -1)
542 while (fgets(curline
, 102, mb
) != NULL
)
545 fcntl (fileno(mb
), F_UNLCK
, &fl
);
547 pline("There is a%s message on this scroll.",
548 seen_one_already
? "nother" : "");
550 msg
= strchr(curline
, ':');
558 pline ("This message is from '%s'.", curline
);
561 if ( (flags
.gmmode
|| flags
.supergmmode
) && !(flags
.gmmessage
)) goto dontread
;
563 msg
[strlen(msg
) - 1] = '\0'; /* kill newline */
564 pline ("It reads: \"%s\".", msg
);
568 /* Game master mode: if active, I (Amy) can send you mail to spawn monsters!
569 * If you turned off the gmmessage option, the monster I selected will be a surprise :) */
572 if (flags
.gmmode
&& !flags
.supergmmode
&& strlen(curline
) == 7 && !strncmpi(curline
, "AmyBSOD", 8)) {
573 if (strlen(msg
) > 9 && !strncmpi(msg
, "genesis ", 8)) {
575 gmmode_genesis(msg
+ 8);
578 if (flags
.supergmmode
) {
579 if (strlen(msg
) > 9 && !strncmpi(msg
, "genesis ", 8)) {
581 gmmode_genesis(msg
+ 8);
585 seen_one_already
= TRUE
;
589 fcntl(fileno(mb
), F_SETLKW
, &fl
);
593 fcntl(fileno(mb
), F_UNLCK
, &fl
);
599 # endif /* SIMPLE_MAIL */
600 # ifdef DEF_MAILREADER /* This implies that UNIX is defined */
601 display_nhwindow(WIN_MESSAGE
, FALSE
);
602 if(!(mr
= nh_getenv("MAILREADER")))
606 (void) execl(mr
, mr
, (char *)0);
607 terminate(EXIT_FAILURE
);
610 # ifndef AMS /* AMS mailboxes are directories */
611 display_file_area(NULL
, mailbox
, TRUE
);
613 # endif /* DEF_MAILREADER */
615 /* get new stat; not entirely correct: there is a small time
616 window where we do not see new mail */
622 pline("It appears to be all gibberish."); /* bail out _professionally_ */
630 extern struct mail_info
*parse_next_broadcast(void);
632 volatile int broadcasts
= 0;
637 struct mail_info
*brdcst
;
639 if(u
.uswallow
|| !flags
.biff
) return;
641 while (broadcasts
> 0) { /* process all trapped broadcasts [until] */
643 if ((brdcst
= parse_next_broadcast()) != 0) {
645 break; /* only handle one real message at a time */
654 # ifdef SHELL /* can't access mail reader without spawning subprocess */
655 const char *txt
, *cmd
;
656 char *p
, buf
[BUFSZ
], qbuf
[BUFSZ
];
659 /* there should be a command hidden beyond the object name */
660 txt
= otmp
->onamelth
? ONAME(otmp
) : "";
662 cmd
= (len
+ 1 < otmp
->onamelth
) ? txt
+ len
+ 1 : (char *) 0;
663 if (!cmd
|| !*cmd
) cmd
= "SPAWN";
665 sprintf(qbuf
, "System command (%s)", cmd
);
667 if (*buf
!= '\033') {
668 for (p
= eos(buf
); p
> buf
; *p
= '\0')
669 if (*--p
!= ' ') break; /* strip trailing spaces */
670 if (*buf
) cmd
= buf
; /* use user entered command */
671 if (!strcmpi(cmd
, "SPAWN") || !strcmp(cmd
, "!"))
672 cmd
= (char *) 0; /* interactive escape */
674 vms_doshell(cmd
, TRUE
);
687 static int laststattime
= 0;
689 if(u
.uswallow
|| !flags
.biff
690 || moves
< laststattime
+ mailckfreq
)
693 laststattime
= moves
;
694 if (lan_mail_check()) {
695 static struct mail_info deliver
= {
696 # ifndef NO_MAILREADER
697 MSG_MAIL
, "I have some mail for you",
699 /* suppress creation and delivery of scroll of mail */
700 MSG_OTHER
, "You have some mail in the outside world",
716 # endif /* LAN_MAIL */