1 /* aNetHack 0.0.1 mail.c $ANH-Date: 1464222344 2016/05/26 00:25:44 $ $ANH-Branch: master $:$ANH-Revision: 1.27 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* aNetHack may be freely redistributed. See license for details. */
11 #endif /* SIMPLE_MAIL */
15 * Notify user when new mail has arrived. Idea by Merlyn Leroy.
17 * The mail daemon can move with less than usual restraint. It can:
18 * - move diagonally from a door
19 * - use secret and closed doors
20 * - run through a monster ("Gangway!", etc.)
21 * - run over pools & traps
23 * Possible extensions:
24 * - Open the file MAIL and do fstat instead of stat for efficiency.
25 * (But sh uses stat, so this cannot be too bad.)
26 * - Examine the mail and produce a scroll of mail named "From somebody".
27 * - Invoke MAILREADER in such a way that only this single mail is read.
28 * - Do something to the text when the scroll is enchanted or cancelled.
29 * - Make the daemon always appear at a stairwell, and have it find a
32 * Note by Olaf Seibert: On the Amiga, we usually don't get mail. So we go
33 * through most of the effects at 'random' moments.
34 * Note by Paul Winner: The MSDOS port also 'fakes' the mail daemon at
38 STATIC_DCL boolean
FDECL(md_start
, (coord
*));
39 STATIC_DCL boolean
FDECL(md_stop
, (coord
*, coord
*));
40 STATIC_DCL boolean
FDECL(md_rush
, (struct monst
*, int, int));
41 STATIC_DCL
void FDECL(newmail
, (struct mail_info
*));
43 extern char *viz_rmin
, *viz_rmax
; /* line-of-sight limits (vision.c) */
45 #if !defined(UNIX) && !defined(VMS)
52 /* DON'T trust all Unices to declare getpwuid() in <pwd.h> */
53 #if !defined(_BULL_SOURCE) && !defined(__sgi) && !defined(_M_UNIX)
54 #if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__))
55 /* DO trust all SVR4 to typedef uid_t in <sys/types.h> (probably to a long) */
56 #if defined(POSIX_TYPES) || defined(SVR4) || defined(HPUX)
57 extern struct passwd
*FDECL(getpwuid
, (uid_t
));
59 extern struct passwd
*FDECL(getpwuid
, (int));
63 static struct stat omstat
, nmstat
;
64 static char *mailbox
= (char *) 0;
65 static long laststattime
;
67 #if !defined(MAILPATH) && defined(AMS) /* Just a placeholder for AMS */
68 #define MAILPATH "/dev/null"
70 #if !defined(MAILPATH) && (defined(LINUX) || defined(__osf__))
71 #define MAILPATH "/var/spool/mail/"
73 #if !defined(MAILPATH) && defined(__FreeBSD__)
74 #define MAILPATH "/var/mail/"
76 #if !defined(MAILPATH) && (defined(BSD) || defined(ULTRIX))
77 #define MAILPATH "/usr/spool/mail/"
79 #if !defined(MAILPATH) && (defined(SYSV) || defined(HPUX))
80 #define MAILPATH "/usr/mail/"
87 free((genericptr_t
) mailbox
), mailbox
= (char *) 0;
94 if ((emailbox
= nh_getenv("MAIL")) != 0) {
95 mailbox
= (char *) alloc((unsigned) strlen(emailbox
));
96 Strcpy(mailbox
, emailbox
);
101 struct passwd ppasswd
;
103 (void) memcpy(&ppasswd
, getpwuid(getuid()), sizeof(struct passwd
));
104 if (ppasswd
.pw_dir
) {
105 mailbox
= (char *) alloc((unsigned) strlen(ppasswd
.pw_dir
)
106 + sizeof(AMS_MAILBOX
));
107 Strcpy(mailbox
, ppasswd
.pw_dir
);
108 Strcat(mailbox
, AMS_MAILBOX
);
112 const char *pw_name
= getpwuid(getuid())->pw_name
;
113 mailbox
= (char *) alloc(sizeof(MAILPATH
) + strlen(pw_name
));
114 Strcpy(mailbox
, MAILPATH
);
115 Strcat(mailbox
, pw_name
);
121 if (stat(mailbox
, &omstat
)) {
122 #ifdef PERMANENT_MAILBOX
123 pline("Cannot get status of MAIL=\"%s\".", mailbox
);
133 * Pick coordinates for a starting position for the mail daemon. Called
134 * from newmail() and newphone().
140 coord testcc
; /* scratch coordinates */
141 int row
; /* current row we are checking */
142 int lax
; /* if TRUE, pick a position in sight. */
143 int dd
; /* distance to current point */
144 int max_distance
; /* max distance found so far */
147 * If blind and not telepathic, then it doesn't matter what we pick ---
148 * the hero is not going to see it anyway. So pick a nearby position.
150 if (Blind
&& !Blind_telepat
) {
151 if (!enexto(startp
, u
.ux
, u
.uy
, (struct permonst
*) 0))
152 return FALSE
; /* no good positions */
157 * Arrive at an up or down stairwell if it is in line of sight from the
160 if (couldsee(upstair
.sx
, upstair
.sy
)) {
161 startp
->x
= upstair
.sx
;
162 startp
->y
= upstair
.sy
;
165 if (couldsee(dnstair
.sx
, dnstair
.sy
)) {
166 startp
->x
= dnstair
.sx
;
167 startp
->y
= dnstair
.sy
;
172 * Try to pick a location out of sight next to the farthest position away
173 * from the hero. If this fails, try again, just picking the farthest
174 * position that could be seen. What we really ought to be doing is
175 * finding a path from a stairwell...
177 * The arrays viz_rmin[] and viz_rmax[] are set even when blind. These
178 * are the LOS limits for each row.
180 lax
= 0; /* be picky */
183 for (row
= 0; row
< ROWNO
; row
++) {
184 if (viz_rmin
[row
] < viz_rmax
[row
]) {
185 /* There are valid positions on this row. */
186 dd
= distu(viz_rmin
[row
], row
);
187 if (dd
> max_distance
) {
191 startp
->x
= viz_rmin
[row
];
193 } else if (enexto(&testcc
, (xchar
) viz_rmin
[row
], row
,
194 (struct permonst
*) 0)
195 && !cansee(testcc
.x
, testcc
.y
)
196 && couldsee(testcc
.x
, testcc
.y
)) {
201 dd
= distu(viz_rmax
[row
], row
);
202 if (dd
> max_distance
) {
206 startp
->x
= viz_rmax
[row
];
208 } else if (enexto(&testcc
, (xchar
) viz_rmax
[row
], row
,
209 (struct permonst
*) 0)
210 && !cansee(testcc
.x
, testcc
.y
)
211 && couldsee(testcc
.x
, testcc
.y
)) {
219 if (max_distance
< 0) {
221 lax
= 1; /* just find a position */
231 * Try to choose a stopping point as near as possible to the starting
232 * position while still adjacent to the hero. If all else fails, try
233 * enexto(). Use enexto() as a last resort because enexto() chooses
234 * its point randomly, which is not what we want.
237 md_stop(stopp
, startp
)
238 coord
*stopp
; /* stopping position (we fill it in) */
239 coord
*startp
; /* starting position (read only) */
241 int x
, y
, distance
, min_distance
= -1;
243 for (x
= u
.ux
- 1; x
<= u
.ux
+ 1; x
++)
244 for (y
= u
.uy
- 1; y
<= u
.uy
+ 1; y
++) {
245 if (!isok(x
, y
) || (x
== u
.ux
&& y
== u
.uy
))
248 if (accessible(x
, y
) && !MON_AT(x
, y
)) {
249 distance
= dist2(x
, y
, startp
->x
, startp
->y
);
250 if (min_distance
< 0 || distance
< min_distance
251 || (distance
== min_distance
&& rn2(2))) {
254 min_distance
= distance
;
259 /* If we didn't find a good spot, try enexto(). */
260 if (min_distance
< 0 && !enexto(stopp
, u
.ux
, u
.uy
, &mons
[PM_MAIL_DAEMON
]))
266 /* Let the mail daemon have a larger vocabulary. */
267 static NEARDATA
const char *mail_text
[] = { "Gangway!", "Look out!",
269 #define md_exclamations() (mail_text[rn2(3)])
272 * Make the mail daemon run through the dungeon. The daemon will run over
273 * any monsters that are in its path, but will replace them later. Return
274 * FALSE if the md gets stuck in a position where there is a monster. Return
280 register int tx
, ty
; /* destination of mail daemon */
282 struct monst
*mon
; /* displaced monster */
283 register int dx
, dy
; /* direction counters */
284 int fx
= md
->mx
, fy
= md
->my
; /* current location */
285 int nfx
= fx
, nfy
= fy
, /* new location */
286 d1
, d2
; /* shortest distances */
289 * It is possible that the monster at (fx,fy) is not the md when:
290 * the md rushed the hero and failed, and is now starting back.
292 if (m_at(fx
, fy
) == md
) {
293 remove_monster(fx
, fy
); /* pick up from orig position */
298 * At the beginning and exit of this loop, md is not placed in the
302 /* Find a good location next to (fx,fy) closest to (tx,ty). */
303 d1
= dist2(fx
, fy
, tx
, ty
);
304 for (dx
= -1; dx
<= 1; dx
++)
305 for (dy
= -1; dy
<= 1; dy
++)
306 if ((dx
|| dy
) && isok(fx
+ dx
, fy
+ dy
)
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
)
320 fx
= nfx
; /* this is our new position */
323 /* Break if the md reaches its destination. */
324 if (fx
== tx
&& fy
== ty
)
327 if ((mon
= m_at(fx
, fy
)) != 0) /* save monster at this position */
328 verbalize1(md_exclamations());
329 else if (fx
== u
.ux
&& fy
== u
.uy
)
330 verbalize("Excuse me.");
332 place_monster(md
, fx
, fy
); /* put md down */
333 newsym(fx
, fy
); /* see it */
334 flush_screen(0); /* make sure md shows up */
335 delay_output(); /* wait a little bit */
337 /* Remove md from the dungeon. Restore original mon, if necessary. */
339 if ((mon
->mx
!= fx
) || (mon
->my
!= fy
))
340 place_worm_seg(mon
, fx
, fy
);
342 place_monster(mon
, fx
, fy
);
344 remove_monster(fx
, fy
);
349 * Check for a monster at our stopping position (this is possible, but
350 * very unlikely). If one exists, then have the md leave in disgust.
352 if ((mon
= m_at(fx
, fy
)) != 0) {
353 place_monster(md
, fx
, fy
); /* display md with text below */
355 verbalize("This place's too crowded. I'm outta here.");
357 if ((mon
->mx
!= fx
) || (mon
->my
!= fy
)) /* put mon back */
358 place_worm_seg(mon
, fx
, fy
);
360 place_monster(mon
, fx
, fy
);
366 place_monster(md
, fx
, fy
); /* place at final spot */
369 delay_output(); /* wait a little bit */
374 /* Deliver a scroll of mail. */
378 struct mail_info
*info
;
382 boolean message_seen
= FALSE
;
384 /* Try to find good starting and stopping places. */
385 if (!md_start(&start
) || !md_stop(&stop
, &start
))
388 /* Make the daemon. Have it rush towards the hero. */
389 if (!(md
= makemon(&mons
[PM_MAIL_DAEMON
], start
.x
, start
.y
, NO_MM_FLAGS
)))
391 if (!md_rush(md
, stop
.x
, stop
.y
))
395 verbalize("%s, %s! %s.", Hello(md
), plname
, info
->display_txt
);
397 if (info
->message_typ
) {
398 struct obj
*obj
= mksobj(SCR_MAIL
, FALSE
, FALSE
);
400 if (info
->object_nam
)
401 obj
= oname(obj
, info
->object_nam
);
402 if (info
->response_cmd
)
403 new_omailcmd(obj
, info
->response_cmd
);
405 if (distu(md
->mx
, md
->my
) > 2)
407 display_nhwindow(WIN_MESSAGE
, FALSE
);
408 obj
= hold_another_object(obj
, "Oops!", (const char *) 0,
412 /* zip back to starting location */
414 (void) md_rush(md
, start
.x
, start
.y
);
416 /* deliver some classes of messages even if no daemon ever shows up */
418 if (!message_seen
&& info
->message_typ
== MSG_OTHER
)
419 pline("Hark! \"%s.\"", info
->display_txt
);
422 #if !defined(UNIX) && !defined(VMS)
427 if (u
.uswallow
|| !flags
.biff
)
429 if (mustgetmail
< 0) {
430 #if defined(AMIGA) || defined(MSDOS) || defined(TOS)
431 mustgetmail
= (moves
< 2000) ? (100 + rn2(2000)) : (2000 + rn2(3000));
435 if (--mustgetmail
<= 0) {
436 static struct mail_info deliver
= {
437 MSG_MAIL
, "I have some mail for you", 0, 0
447 struct obj
*otmp UNUSED
;
449 static char *junk
[] = {
450 NULL
, /* placeholder for "Report bugs to <devteam@anethack.org>.", */
451 "Please disregard previous letter.", "Welcome to aNetHack.",
453 "Only Amiga makes it possible.", "CATS have all the answers.",
455 "This mail complies with the Yendorian Anti-Spam Act (YASA)",
456 "Please find enclosed a small token to represent your Owlbear",
457 "**FR33 P0T10N 0F FULL H34L1NG**",
458 "Please return to sender (Asmodeus)",
459 "Buy a potion of gain level for only $19.99! Guaranteed to be blessed!",
460 "Invitation: Visit the aNetHack web site at http://www.anethack.org!"
463 /* XXX replace with more general substitution code and add local
464 * contact message. Also use DEVTEAM_URL */
465 if (junk
[0] == NULL
) {
466 #define BUGS_FORMAT "Report bugs to <%s>."
467 /* +2 from '%s' suffices as substitute for usual +1 for terminator */
468 junk
[0] = (char *) alloc(strlen(BUGS_FORMAT
) + strlen(DEVTEAM_EMAIL
));
469 Sprintf(junk
[0], BUGS_FORMAT
, DEVTEAM_EMAIL
);
473 pline("Unfortunately you cannot see what it says.");
475 pline("It reads: \"%s\"", junk
[rn2(SIZE(junk
))]);
478 #endif /* !UNIX && !VMS */
485 ck_server_admin_msg();
487 if (!mailbox
|| u
.uswallow
|| !flags
.biff
489 || moves
< laststattime
+ MAILCKFREQ
494 laststattime
= moves
;
495 if (stat(mailbox
, &nmstat
)) {
496 #ifdef PERMANENT_MAILBOX
497 pline("Cannot get status of MAIL=\"%s\" anymore.", mailbox
);
502 } else if (nmstat
.st_mtime
> omstat
.st_mtime
) {
503 if (nmstat
.st_size
) {
504 static struct mail_info deliver
= {
505 #ifndef NO_MAILREADER
506 MSG_MAIL
, "I have some mail for you",
508 /* suppress creation and delivery of scroll of mail */
509 MSG_OTHER
, "You have some mail in the outside world",
515 getmailstatus(); /* might be too late ... */
519 #if defined(SIMPLE_MAIL) || defined(SERVER_ADMIN_MSG)
521 read_simplemail(mbox
, adminmsg
)
525 FILE* mb
= fopen(mbox
, "r");
526 char curline
[128], *msg
;
527 boolean seen_one_already
= FALSE
;
529 struct flock fl
= { 0 };
531 const char *msgfrom
= adminmsg
532 ? "The voice of %s booms through the caverns:"
533 : "This message is from '%s'.";
540 fl
.l_whence
= SEEK_SET
;
546 /* Allow this call to block. */
549 && fcntl (fileno (mb
), F_SETLKW
, &fl
) == -1
554 while (fgets(curline
, 128, mb
) != NULL
) {
558 fcntl (fileno(mb
), F_UNLCK
, &fl
);
560 pline("There is a%s message on this scroll.",
561 seen_one_already
? "nother" : "");
563 msg
= strchr(curline
, ':');
570 msg
[strlen(msg
) - 1] = '\0'; /* kill newline */
572 pline(msgfrom
, curline
);
576 pline("It reads: \"%s\".", msg
);
578 seen_one_already
= TRUE
;
583 fcntl(fileno(mb
), F_SETLKW
, &fl
);
591 fcntl(fileno(mb
), F_UNLCK
, &fl
);
596 display_nhwindow(WIN_MESSAGE
, TRUE
);
601 /* bail out _professionally_ */
603 pline("It appears to be all gibberish.");
605 #endif /* SIMPLE_MAIL */
608 ck_server_admin_msg()
610 #ifdef SERVER_ADMIN_MSG
611 static struct stat ost
,nst
;
612 static long lastchk
= 0;
614 if (moves
< lastchk
+ SERVER_ADMIN_MSG_CKFREQ
) return;
617 if (!stat(SERVER_ADMIN_MSG
, &nst
)) {
618 if (nst
.st_mtime
> ost
.st_mtime
)
619 read_simplemail(SERVER_ADMIN_MSG
, TRUE
);
620 ost
.st_mtime
= nst
.st_mtime
;
622 #endif /* SERVER_ADMIN_MSG */
628 struct obj
*otmp UNUSED
;
630 #ifdef DEF_MAILREADER /* This implies that UNIX is defined */
631 register const char *mr
= 0;
632 #endif /* DEF_MAILREADER */
634 read_simplemail(mailbox
, FALSE
);
636 #endif /* SIMPLE_MAIL */
637 #ifdef DEF_MAILREADER /* This implies that UNIX is defined */
638 display_nhwindow(WIN_MESSAGE
, FALSE
);
639 if (!(mr
= nh_getenv("MAILREADER")))
643 (void) execl(mr
, mr
, (char *) 0);
644 terminate(EXIT_FAILURE
);
647 #ifndef AMS /* AMS mailboxes are directories */
648 display_file(mailbox
, TRUE
);
650 #endif /* DEF_MAILREADER */
652 /* get new stat; not entirely correct: there is a small time
653 window where we do not see new mail */
661 extern NDECL(struct mail_info
*parse_next_broadcast
);
663 volatile int broadcasts
= 0;
668 struct mail_info
*brdcst
;
670 if (u
.uswallow
|| !flags
.biff
)
673 while (broadcasts
> 0) { /* process all trapped broadcasts [until] */
675 if ((brdcst
= parse_next_broadcast()) != 0) {
677 break; /* only handle one real message at a time */
686 #ifdef SHELL /* can't access mail reader without spawning subprocess */
687 const char *txt
, *cmd
;
688 char *p
, buf
[BUFSZ
], qbuf
[BUFSZ
];
691 /* there should be a command in OMAILCMD */
697 if (has_omailcmd(otmp
))
698 cmd
= OMAILCMD(otmp
);
702 Sprintf(qbuf
, "System command (%s)", cmd
);
704 if (*buf
!= '\033') {
705 for (p
= eos(buf
); p
> buf
; *p
= '\0')
707 break; /* strip trailing spaces */
709 cmd
= buf
; /* use user entered command */
710 if (!strcmpi(cmd
, "SPAWN") || !strcmp(cmd
, "!"))
711 cmd
= (char *) 0; /* interactive escape */
713 vms_doshell(cmd
, TRUE
);