Merge from vendor branch CVS:
[dragonfly.git] / games / hack / hack.end.c
1 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
2 /* hack.end.c - version 1.0.3 */
3 /* $FreeBSD: src/games/hack/hack.end.c,v 1.4 1999/11/16 10:26:36 marcel Exp $ */
4 /* $DragonFly: src/games/hack/hack.end.c,v 1.3 2004/11/06 12:29:17 eirikn Exp $ */
5
6 #include "hack.h"
7 #include <stdio.h>
8 #include <signal.h>
9 #define Sprintf (void) sprintf
10 extern char plname[], pl_character[];
11 extern char *itoa(), *ordin(), *eos();
12
13 xchar maxdlevel = 1;
14
15 void
16 done1()
17 {
18         (void) signal(SIGINT,SIG_IGN);
19         pline("Really quit?");
20         if(readchar() != 'y') {
21                 (void) signal(SIGINT,done1);
22                 clrlin();
23                 (void) fflush(stdout);
24                 if(multi > 0) nomul(0);
25                 return;
26         }
27         done("quit");
28         /* NOTREACHED */
29 }
30
31 int done_stopprint;
32 int done_hup;
33
34 void
35 done_intr(){
36         done_stopprint++;
37         (void) signal(SIGINT, SIG_IGN);
38         (void) signal(SIGQUIT, SIG_IGN);
39 }
40
41 void
42 done_hangup(){
43         done_hup++;
44         (void) signal(SIGHUP, SIG_IGN);
45         done_intr();
46 }
47
48 done_in_by(mtmp) struct monst *mtmp; {
49 static char buf[BUFSZ];
50         pline("You die ...");
51         if(mtmp->data->mlet == ' '){
52                 Sprintf(buf, "the ghost of %s", (char *) mtmp->mextra);
53                 killer = buf;
54         } else if(mtmp->mnamelth) {
55                 Sprintf(buf, "%s called %s",
56                         mtmp->data->mname, NAME(mtmp));
57                 killer = buf;
58         } else if(mtmp->minvis) {
59                 Sprintf(buf, "invisible %s", mtmp->data->mname);
60                 killer = buf;
61         } else killer = mtmp->data->mname;
62         done("died");
63 }
64
65 /* called with arg "died", "drowned", "escaped", "quit", "choked", "panicked",
66    "burned", "starved" or "tricked" */
67 /* Be careful not to call panic from here! */
68 done(st1)
69 char *st1;
70 {
71
72 #ifdef WIZARD
73         if(wizard && *st1 == 'd'){
74                 u.uswldtim = 0;
75                 if(u.uhpmax < 0) u.uhpmax = 100;        /* arbitrary */
76                 u.uhp = u.uhpmax;
77                 pline("For some reason you are still alive.");
78                 flags.move = 0;
79                 if(multi > 0) multi = 0; else multi = -1;
80                 flags.botl = 1;
81                 return;
82         }
83 #endif /* WIZARD */
84         (void) signal(SIGINT, done_intr);
85         (void) signal(SIGQUIT, done_intr);
86         (void) signal(SIGHUP, done_hangup);
87         if(*st1 == 'q' && u.uhp < 1){
88                 st1 = "died";
89                 killer = "quit while already on Charon's boat";
90         }
91         if(*st1 == 's') killer = "starvation"; else
92         if(*st1 == 'd' && st1[1] == 'r') killer = "drowning"; else
93         if(*st1 == 'p') killer = "panic"; else
94         if(*st1 == 't') killer = "trickery"; else
95         if(!index("bcd", *st1)) killer = st1;
96         paybill();
97         clearlocks();
98         if(flags.toplin == 1) more();
99         if(index("bcds", *st1)){
100 #ifdef WIZARD
101             if(!wizard)
102 #endif /* WIZARD */
103                 savebones();
104                 if(!flags.notombstone)
105                         outrip();
106         }
107         if(*st1 == 'c') killer = st1;           /* after outrip() */
108         settty((char *) 0);     /* does a clear_screen() */
109         if(!done_stopprint)
110                 printf("Goodbye %s %s...\n\n", pl_character, plname);
111         { long int tmp;
112           tmp = u.ugold - u.ugold0;
113           if(tmp < 0)
114                 tmp = 0;
115           if(*st1 == 'd' || *st1 == 'b')
116                 tmp -= tmp/10;
117           u.urexp += tmp;
118           u.urexp += 50 * maxdlevel;
119           if(maxdlevel > 20)
120                 u.urexp += 1000*((maxdlevel > 30) ? 10 : maxdlevel - 20);
121         }
122         if(*st1 == 'e') {
123                 extern struct monst *mydogs;
124                 struct monst *mtmp;
125                 struct obj *otmp;
126                 int i;
127                 unsigned worthlessct = 0;
128                 boolean has_amulet = FALSE;
129
130                 killer = st1;
131                 keepdogs();
132                 mtmp = mydogs;
133                 if(mtmp) {
134                         if(!done_stopprint) printf("You");
135                         while(mtmp) {
136                                 if(!done_stopprint)
137                                         printf(" and %s", monnam(mtmp));
138                                 if(mtmp->mtame)
139                                         u.urexp += mtmp->mhp;
140                                 mtmp = mtmp->nmon;
141                         }
142                         if(!done_stopprint)
143                     printf("\nescaped from the dungeon with %ld points,\n",
144                         u.urexp);
145                 } else
146                 if(!done_stopprint)
147                   printf("You escaped from the dungeon with %ld points,\n",
148                     u.urexp);
149                 for(otmp = invent; otmp; otmp = otmp->nobj) {
150                         if(otmp->olet == GEM_SYM){
151                                 objects[otmp->otyp].oc_name_known = 1;
152                                 i = otmp->quan*objects[otmp->otyp].g_val;
153                                 if(i == 0) {
154                                         worthlessct += otmp->quan;
155                                         continue;
156                                 }
157                                 u.urexp += i;
158                                 if(!done_stopprint)
159                                   printf("\t%s (worth %d Zorkmids),\n",
160                                     doname(otmp), i);
161                         } else if(otmp->olet == AMULET_SYM) {
162                                 otmp->known = 1;
163                                 i = (otmp->spe < 0) ? 2 : 5000;
164                                 u.urexp += i;
165                                 if(!done_stopprint)
166                                   printf("\t%s (worth %d Zorkmids),\n",
167                                     doname(otmp), i);
168                                 if(otmp->spe >= 0) {
169                                         has_amulet = TRUE;
170                                         killer = "escaped (with amulet)";
171                                 }
172                         }
173                 }
174                 if(worthlessct) if(!done_stopprint)
175                   printf("\t%u worthless piece%s of coloured glass,\n",
176                   worthlessct, plur(worthlessct));
177                 if(has_amulet) u.urexp *= 2;
178         } else
179                 if(!done_stopprint)
180                   printf("You %s on dungeon level %d with %ld points,\n",
181                     st1, dlevel, u.urexp);
182         if(!done_stopprint)
183           printf("and %ld piece%s of gold, after %ld move%s.\n",
184             u.ugold, plur(u.ugold), moves, plur(moves));
185         if(!done_stopprint)
186   printf("You were level %u with a maximum of %d hit points when you %s.\n",
187             u.ulevel, u.uhpmax, st1);
188         if(*st1 == 'e' && !done_stopprint){
189                 getret();       /* all those pieces of coloured glass ... */
190                 cls();
191         }
192 #ifdef WIZARD
193         if(!wizard)
194 #endif /* WIZARD */
195                 topten();
196         if(done_stopprint) printf("\n\n");
197         exit(0);
198 }
199
200 #define newttentry() (struct toptenentry *) alloc(sizeof(struct toptenentry))
201 #define NAMSZ   8
202 #define DTHSZ   40
203 #define PERSMAX 1
204 #define POINTSMIN       1       /* must be > 0 */
205 #define ENTRYMAX        100     /* must be >= 10 */
206 #define PERS_IS_UID             /* delete for PERSMAX per name; now per uid */
207 struct toptenentry {
208         struct toptenentry *tt_next;
209         long int points;
210         int level,maxlvl,hp,maxhp;
211         int uid;
212         char plchar;
213         char sex;
214         char name[NAMSZ+1];
215         char death[DTHSZ+1];
216         char date[7];           /* yymmdd */
217 } *tt_head;
218
219 topten(){
220         int uid = getuid();
221         int rank, rank0 = -1, rank1 = 0;
222         int occ_cnt = PERSMAX;
223         struct toptenentry *t0, *t1, *tprev;
224         char *recfile = RECORD;
225         char *reclock = "record_lock";
226         int sleepct = 300;
227         FILE *rfile;
228         int flg = 0;
229         extern char *getdate();
230 #define HUP     if(!done_hup)
231         while(link(recfile, reclock) == -1) {
232                 HUP perror(reclock);
233                 if(!sleepct--) {
234                         HUP puts("I give up. Sorry.");
235                         HUP puts("Perhaps there is an old record_lock around?");
236                         return;
237                 }
238                 HUP printf("Waiting for access to record file. (%d)\n",
239                         sleepct);
240                 HUP (void) fflush(stdout);
241                 sleep(1);
242         }
243         if(!(rfile = fopen(recfile,"r"))){
244                 HUP puts("Cannot open record file!");
245                 goto unlock;
246         }
247         HUP (void) putchar('\n');
248
249         /* create a new 'topten' entry */
250         t0 = newttentry();
251         t0->level = dlevel;
252         t0->maxlvl = maxdlevel;
253         t0->hp = u.uhp;
254         t0->maxhp = u.uhpmax;
255         t0->points = u.urexp;
256         t0->plchar = pl_character[0];
257         t0->sex = (flags.female ? 'F' : 'M');
258         t0->uid = uid;
259         (void) strncpy(t0->name, plname, NAMSZ);
260         (t0->name)[NAMSZ] = 0;
261         (void) strncpy(t0->death, killer, DTHSZ);
262         (t0->death)[DTHSZ] = 0;
263         (void) strcpy(t0->date, getdate());
264
265         /* assure minimum number of points */
266         if(t0->points < POINTSMIN)
267                 t0->points = 0;
268
269         t1 = tt_head = newttentry();
270         tprev = 0;
271         /* rank0: -1 undefined, 0 not_on_list, n n_th on list */
272         for(rank = 1; ; ) {
273           if(fscanf(rfile, "%6s %d %d %d %d %d %ld %c%c %[^,],%[^\n]",
274                 t1->date, &t1->uid,
275                 &t1->level, &t1->maxlvl,
276                 &t1->hp, &t1->maxhp, &t1->points,
277                 &t1->plchar, &t1->sex, t1->name, t1->death) != 11
278           || t1->points < POINTSMIN)
279                         t1->points = 0;
280           if(rank0 < 0 && t1->points < t0->points) {
281                 rank0 = rank++;
282                 if(tprev == 0)
283                         tt_head = t0;
284                 else
285                         tprev->tt_next = t0;
286                 t0->tt_next = t1;
287                 occ_cnt--;
288                 flg++;          /* ask for a rewrite */
289           } else
290                 tprev = t1;
291           if(t1->points == 0) break;
292           if(
293 #ifdef PERS_IS_UID
294              t1->uid == t0->uid &&
295 #else
296              strncmp(t1->name, t0->name, NAMSZ) == 0 &&
297 #endif /* PERS_IS_UID */
298              t1->plchar == t0->plchar && --occ_cnt <= 0){
299                 if(rank0 < 0){
300                         rank0 = 0;
301                         rank1 = rank;
302         HUP printf("You didn't beat your previous score of %ld points.\n\n",
303                                 t1->points);
304                 }
305                 if(occ_cnt < 0){
306                         flg++;
307                         continue;
308                 }
309           }
310           if(rank <= ENTRYMAX){
311                 t1 = t1->tt_next = newttentry();
312                 rank++;
313           }
314           if(rank > ENTRYMAX){
315                 t1->points = 0;
316                 break;
317           }
318         }
319         if(flg) {       /* rewrite record file */
320                 (void) fclose(rfile);
321                 if(!(rfile = fopen(recfile,"w"))){
322                         HUP puts("Cannot write record file\n");
323                         goto unlock;
324                 }
325
326                 if(!done_stopprint) if(rank0 > 0){
327                     if(rank0 <= 10)
328                         puts("You made the top ten list!\n");
329                     else
330                 printf("You reached the %d%s place on the top %d list.\n\n",
331                         rank0, ordin(rank0), ENTRYMAX);
332                 }
333         }
334         if(rank0 == 0) rank0 = rank1;
335         if(rank0 <= 0) rank0 = rank;
336         if(!done_stopprint) outheader();
337         t1 = tt_head;
338         for(rank = 1; t1->points != 0; rank++, t1 = t1->tt_next) {
339           if(flg) fprintf(rfile,"%6s %d %d %d %d %d %ld %c%c %s,%s\n",
340             t1->date, t1->uid,
341             t1->level, t1->maxlvl,
342             t1->hp, t1->maxhp, t1->points,
343             t1->plchar, t1->sex, t1->name, t1->death);
344           if(done_stopprint) continue;
345           if(rank > flags.end_top &&
346             (rank < rank0-flags.end_around || rank > rank0+flags.end_around)
347             && (!flags.end_own ||
348 #ifdef PERS_IS_UID
349                                   t1->uid != t0->uid ))
350 #else
351                                   strncmp(t1->name, t0->name, NAMSZ)))
352 #endif /* PERS_IS_UID */
353                 continue;
354           if(rank == rank0-flags.end_around &&
355              rank0 > flags.end_top+flags.end_around+1 &&
356              !flags.end_own)
357                 (void) putchar('\n');
358           if(rank != rank0)
359                 (void) outentry(rank, t1, 0);
360           else if(!rank1)
361                 (void) outentry(rank, t1, 1);
362           else {
363                 int t0lth = outentry(0, t0, -1);
364                 int t1lth = outentry(rank, t1, t0lth);
365                 if(t1lth > t0lth) t0lth = t1lth;
366                 (void) outentry(0, t0, t0lth);
367           }
368         }
369         if(rank0 >= rank) if(!done_stopprint)
370                 (void) outentry(0, t0, 1);
371         (void) fclose(rfile);
372 unlock:
373         (void) unlink(reclock);
374 }
375
376 outheader() {
377 char linebuf[BUFSZ];
378 char *bp;
379         (void) strcpy(linebuf, "Number Points  Name");
380         bp = eos(linebuf);
381         while(bp < linebuf + COLNO - 9) *bp++ = ' ';
382         (void) strcpy(bp, "Hp [max]");
383         puts(linebuf);
384 }
385
386 /* so>0: standout line; so=0: ordinary line; so<0: no output, return lth */
387 int
388 outentry(rank,t1,so) struct toptenentry *t1; {
389 boolean quit = FALSE, killed = FALSE, starv = FALSE;
390 char linebuf[BUFSZ];
391         linebuf[0] = 0;
392         if(rank) Sprintf(eos(linebuf), "%3d", rank);
393                 else Sprintf(eos(linebuf), "   ");
394         Sprintf(eos(linebuf), " %6ld %8s", t1->points, t1->name);
395         if(t1->plchar == 'X') Sprintf(eos(linebuf), " ");
396         else Sprintf(eos(linebuf), "-%c ", t1->plchar);
397         if(!strncmp("escaped", t1->death, 7)) {
398           if(!strcmp(" (with amulet)", t1->death+7))
399             Sprintf(eos(linebuf), "escaped the dungeon with amulet");
400           else
401             Sprintf(eos(linebuf), "escaped the dungeon [max level %d]",
402               t1->maxlvl);
403         } else {
404           if(!strncmp(t1->death,"quit",4)) {
405             quit = TRUE;
406             if(t1->maxhp < 3*t1->hp && t1->maxlvl < 4)
407                 Sprintf(eos(linebuf), "cravenly gave up");
408             else
409                 Sprintf(eos(linebuf), "quit");
410           }
411           else if(!strcmp(t1->death,"choked"))
412             Sprintf(eos(linebuf), "choked on %s food",
413                 (t1->sex == 'F') ? "her" : "his");
414           else if(!strncmp(t1->death,"starv",5))
415             Sprintf(eos(linebuf), "starved to death"), starv = TRUE;
416           else Sprintf(eos(linebuf), "was killed"), killed = TRUE;
417           Sprintf(eos(linebuf), " on%s level %d",
418             (killed || starv) ? "" : " dungeon", t1->level);
419           if(t1->maxlvl != t1->level)
420             Sprintf(eos(linebuf), " [max %d]", t1->maxlvl);
421           if(quit && t1->death[4]) Sprintf(eos(linebuf), t1->death + 4);
422         }
423         if(killed) Sprintf(eos(linebuf), " by %s%s",
424           (!strncmp(t1->death, "trick", 5) || !strncmp(t1->death, "the ", 4))
425                 ? "" :
426           index(vowels,*t1->death) ? "an " : "a ",
427           t1->death);
428         Sprintf(eos(linebuf), ".");
429         if(t1->maxhp) {
430           char *bp = eos(linebuf);
431           char hpbuf[10];
432           int hppos;
433           Sprintf(hpbuf, (t1->hp > 0) ? itoa(t1->hp) : "-");
434           hppos = COLNO - 7 - strlen(hpbuf);
435           if(bp <= linebuf + hppos) {
436             while(bp < linebuf + hppos) *bp++ = ' ';
437             (void) strcpy(bp, hpbuf);
438             Sprintf(eos(bp), " [%d]", t1->maxhp);
439           }
440         }
441         if(so == 0) puts(linebuf);
442         else if(so > 0) {
443           char *bp = eos(linebuf);
444           if(so >= COLNO) so = COLNO-1;
445           while(bp < linebuf + so) *bp++ = ' ';
446           *bp = 0;
447           standoutbeg();
448           fputs(linebuf,stdout);
449           standoutend();
450           (void) putchar('\n');
451         }
452         return(strlen(linebuf));
453 }
454
455 char *
456 itoa(a) int a; {
457 static char buf[12];
458         Sprintf(buf,"%d",a);
459         return(buf);
460 }
461
462 char *
463 ordin(n) int n; {
464 int d = n%10;
465         return((d==0 || d>3 || n/10==1) ? "th" : (d==1) ? "st" :
466                 (d==2) ? "nd" : "rd");
467 }
468
469 clearlocks(){
470 int x;
471         (void) signal(SIGHUP,SIG_IGN);
472         for(x = maxdlevel; x >= 0; x--) {
473                 glo(x);
474                 (void) unlink(lock);    /* not all levels need be present */
475         }
476 }
477
478 #ifdef NOSAVEONHANGUP
479 hangup()
480 {
481         (void) signal(SIGINT, SIG_IGN);
482         clearlocks();
483         exit(1);
484 }
485 #endif /* NOSAVEONHANGUP */
486
487 char *
488 eos(s)
489 char *s;
490 {
491         while(*s) s++;
492         return(s);
493 }
494
495 /* it is the callers responsibility to check that there is room for c */
496 charcat(s,c) char *s, c; {
497         while(*s) s++;
498         *s++ = c;
499         *s = 0;
500 }
501
502 /*
503  * Called with args from main if argc >= 0. In this case, list scores as
504  * requested. Otherwise, find scores for the current player (and list them
505  * if argc == -1).
506  */
507 prscore(argc,argv) int argc; char **argv; {
508         extern char *hname;
509         char **players;
510         int playerct;
511         int rank;
512         struct toptenentry *t1, *t2;
513         char *recfile = RECORD;
514         FILE *rfile;
515         int flg = 0;
516         int i;
517 #ifdef nonsense
518         long total_score = 0L;
519         char totchars[10];
520         int totcharct = 0;
521 #endif /* nonsense */
522         int outflg = (argc >= -1);
523 #ifdef PERS_IS_UID
524         int uid = -1;
525 #else
526         char *player0;
527 #endif /* PERS_IS_UID */
528
529         if(!(rfile = fopen(recfile,"r"))){
530                 puts("Cannot open record file!");
531                 return;
532         }
533
534         if(argc > 1 && !strncmp(argv[1], "-s", 2)){
535                 if(!argv[1][2]){
536                         argc--;
537                         argv++;
538                 } else if(!argv[1][3] && index("CFKSTWX", argv[1][2])) {
539                         argv[1]++;
540                         argv[1][0] = '-';
541                 } else  argv[1] += 2;
542         }
543         if(argc <= 1){
544 #ifdef PERS_IS_UID
545                 uid = getuid();
546                 playerct = 0;
547 #else
548                 player0 = plname;
549                 if(!*player0)
550                         player0 = "hackplayer";
551                 playerct = 1;
552                 players = &player0;
553 #endif /* PERS_IS_UID */
554         } else {
555                 playerct = --argc;
556                 players = ++argv;
557         }
558         if(outflg) putchar('\n');
559
560         t1 = tt_head = newttentry();
561         for(rank = 1; ; rank++) {
562           if(fscanf(rfile, "%6s %d %d %d %d %d %ld %c%c %[^,],%[^\n]",
563                 t1->date, &t1->uid,
564                 &t1->level, &t1->maxlvl,
565                 &t1->hp, &t1->maxhp, &t1->points,
566                 &t1->plchar, &t1->sex, t1->name, t1->death) != 11)
567                         t1->points = 0;
568           if(t1->points == 0) break;
569 #ifdef PERS_IS_UID
570           if(!playerct && t1->uid == uid)
571                 flg++;
572           else
573 #endif /* PERS_IS_UID */
574           for(i = 0; i < playerct; i++){
575                 if(strcmp(players[i], "all") == 0 ||
576                    strncmp(t1->name, players[i], NAMSZ) == 0 ||
577                   (players[i][0] == '-' &&
578                    players[i][1] == t1->plchar &&
579                    players[i][2] == 0) ||
580                   (digit(players[i][0]) && rank <= atoi(players[i])))
581                         flg++;
582           }
583           t1 = t1->tt_next = newttentry();
584         }
585         (void) fclose(rfile);
586         if(!flg) {
587             if(outflg) {
588                 printf("Cannot find any entries for ");
589                 if(playerct < 1) printf("you.\n");
590                 else {
591                   if(playerct > 1) printf("any of ");
592                   for(i=0; i<playerct; i++)
593                         printf("%s%s", players[i], (i<playerct-1)?", ":".\n");
594                   printf("Call is: %s -s [playernames]\n", hname);
595                 }
596             }
597             return;
598         }
599
600         if(outflg) outheader();
601         t1 = tt_head;
602         for(rank = 1; t1->points != 0; rank++, t1 = t2) {
603                 t2 = t1->tt_next;
604 #ifdef PERS_IS_UID
605                 if(!playerct && t1->uid == uid)
606                         goto outwithit;
607                 else
608 #endif /* PERS_IS_UID */
609                 for(i = 0; i < playerct; i++){
610                         if(strcmp(players[i], "all") == 0 ||
611                            strncmp(t1->name, players[i], NAMSZ) == 0 ||
612                           (players[i][0] == '-' &&
613                            players[i][1] == t1->plchar &&
614                            players[i][2] == 0) ||
615                           (digit(players[i][0]) && rank <= atoi(players[i]))){
616                         outwithit:
617                                 if(outflg)
618                                     (void) outentry(rank, t1, 0);
619 #ifdef nonsense
620                                 total_score += t1->points;
621                                 if(totcharct < sizeof(totchars)-1)
622                                     totchars[totcharct++] = t1->plchar;
623 #endif /* nonsense */
624                                 break;
625                         }
626                 }
627                 free((char *) t1);
628         }
629 #ifdef nonsense
630         totchars[totcharct] = 0;
631
632         /* We would like to determine whether he is experienced. However,
633            the information collected here only tells about the scores/roles
634            that got into the topten (top 100?). We should maintain a
635            .hacklog or something in his home directory. */
636         flags.beginner = (total_score < 6000);
637         for(i=0; i<6; i++)
638             if(!index(totchars, "CFKSTWX"[i])) {
639                 flags.beginner = 1;
640                 if(!pl_character[0]) pl_character[0] = "CFKSTWX"[i];
641                 break;
642         }
643 #endif /* nonsense */
644 }