Home | History | Annotate | Line # | Download | only in rogue
score.c revision 1.14
      1 /*	$NetBSD: score.c,v 1.14 2008/01/14 03:50:02 dholland Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1988, 1993
      5  *	The Regents of the University of California.  All rights reserved.
      6  *
      7  * This code is derived from software contributed to Berkeley by
      8  * Timothy C. Stoehr.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  * 3. Neither the name of the University nor the names of its contributors
     19  *    may be used to endorse or promote products derived from this software
     20  *    without specific prior written permission.
     21  *
     22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     32  * SUCH DAMAGE.
     33  */
     34 
     35 #include <sys/cdefs.h>
     36 #ifndef lint
     37 #if 0
     38 static char sccsid[] = "@(#)score.c	8.1 (Berkeley) 5/31/93";
     39 #else
     40 __RCSID("$NetBSD: score.c,v 1.14 2008/01/14 03:50:02 dholland Exp $");
     41 #endif
     42 #endif /* not lint */
     43 
     44 /*
     45  * score.c
     46  *
     47  * This source herein may be modified and/or distributed by anybody who
     48  * so desires, with the following restrictions:
     49  *    1.)  No portion of this notice shall be removed.
     50  *    2.)  Credit shall not be taken for the creation of this source.
     51  *    3.)  This code is not to be traded, sold, or used for personal
     52  *         gain or profit.
     53  *
     54  */
     55 
     56 #include <stdio.h>
     57 #include "rogue.h"
     58 #include "pathnames.h"
     59 
     60 void
     61 killed_by(const object *monster, short other)
     62 {
     63 	const char *mechanism = "killed by something unknown (?)";
     64 	char mechanism_buf[128];
     65 	const char *article;
     66 	char message_buf[128];
     67 
     68 	md_ignore_signals();
     69 
     70 	if (other != QUIT) {
     71 		rogue.gold = ((rogue.gold * 9) / 10);
     72 	}
     73 
     74 	if (other) {
     75 		switch(other) {
     76 		case HYPOTHERMIA:
     77 			mechanism = "died of hypothermia";
     78 			break;
     79 		case STARVATION:
     80 			mechanism = "died of starvation";
     81 			break;
     82 		case POISON_DART:
     83 			mechanism = "killed by a dart";
     84 			break;
     85 		case QUIT:
     86 			mechanism = "quit";
     87 			break;
     88 		case KFIRE:
     89 			mechanism = "killed by fire";
     90 			break;
     91 		}
     92 	} else {
     93 		if (is_vowel(m_names[monster->m_char - 'A'][0])) {
     94 			article = "an";
     95 		} else {
     96 			article = "a";
     97 		}
     98 		snprintf(mechanism_buf, sizeof(mechanism_buf),
     99 			"Killed by %s %s",
    100 			 article, m_names[monster->m_char - 'A']);
    101 		mechanism = mechanism_buf;
    102 	}
    103 	snprintf(message_buf, sizeof(message_buf),
    104 		 "%s with %ld gold", mechanism, rogue.gold);
    105 
    106 	if ((!other) && (!no_skull)) {
    107 		clear();
    108 		mvaddstr(4, 32, "__---------__");
    109 		mvaddstr(5, 30, "_~             ~_");
    110 		mvaddstr(6, 29, "/                 \\");
    111 		mvaddstr(7, 28, "~                   ~");
    112 		mvaddstr(8, 27, "/                     \\");
    113 		mvaddstr(9, 27, "|    XXXX     XXXX    |");
    114 		mvaddstr(10, 27, "|    XXXX     XXXX    |");
    115 		mvaddstr(11, 27, "|    XXX       XXX    |");
    116 		mvaddstr(12, 28, "\\         @         /");
    117 		mvaddstr(13, 29, "--\\     @@@     /--");
    118 		mvaddstr(14, 30, "| |    @@@    | |");
    119 		mvaddstr(15, 30, "| |           | |");
    120 		mvaddstr(16, 30, "| vvVvvvvvvvVvv |");
    121 		mvaddstr(17, 30, "|  ^^^^^^^^^^^  |");
    122 		mvaddstr(18, 31, "\\_           _/");
    123 		mvaddstr(19, 33, "~---------~");
    124 		center(21, nick_name);
    125 		center(22, message_buf);
    126 	} else {
    127 		messagef(0, "%s", message_buf);
    128 	}
    129 	messagef(0, "%s", "");		/* gcc objects to just "" */
    130 	put_scores(monster, other);
    131 }
    132 
    133 void
    134 win(void)
    135 {
    136 	unwield(rogue.weapon);		/* disarm and relax */
    137 	unwear(rogue.armor);
    138 	un_put_on(rogue.left_ring);
    139 	un_put_on(rogue.right_ring);
    140 
    141 	clear();
    142 	mvaddstr(10, 11, "@   @  @@@   @   @      @  @  @   @@@   @   @   @");
    143 	mvaddstr(11, 11, " @ @  @   @  @   @      @  @  @  @   @  @@  @   @");
    144 	mvaddstr(12, 11, "  @   @   @  @   @      @  @  @  @   @  @ @ @   @");
    145 	mvaddstr(13, 11, "  @   @   @  @   @      @  @  @  @   @  @  @@");
    146 	mvaddstr(14, 11, "  @    @@@    @@@        @@ @@    @@@   @   @   @");
    147 	mvaddstr(17, 11, "Congratulations,  you have  been admitted  to  the");
    148 	mvaddstr(18, 11, "Fighters' Guild.   You return home,  sell all your");
    149 	mvaddstr(19, 11, "treasures at great profit and retire into comfort.");
    150 	messagef(0, "%s", "");		/* gcc objects to just "" */
    151 	messagef(0, "%s", "");		/* gcc objects to just "" */
    152 	id_all();
    153 	sell_pack();
    154 	put_scores(NULL, WIN);
    155 }
    156 
    157 void
    158 quit(boolean from_intrpt)
    159 {
    160 	char buf[DCOLS];
    161 	short i, orow, ocol;
    162 	boolean mc;
    163 
    164 	orow = ocol = 0;
    165 	mc = FALSE;
    166 	md_ignore_signals();
    167 
    168 	if (from_intrpt) {
    169 		orow = rogue.row;
    170 		ocol = rogue.col;
    171 
    172 		mc = msg_cleared;
    173 
    174 		for (i = 0; i < DCOLS; i++) {
    175 			buf[i] = mvinch(0, i);
    176 		}
    177 	}
    178 	check_message();
    179 	messagef(1, "really quit?");
    180 	if (rgetchar() != 'y') {
    181 		md_heed_signals();
    182 		check_message();
    183 		if (from_intrpt) {
    184 			for (i = 0; i < DCOLS; i++) {
    185 				mvaddch(0, i, buf[i]);
    186 			}
    187 			msg_cleared = mc;
    188 			move(orow, ocol);
    189 			refresh();
    190 		}
    191 		return;
    192 	}
    193 	if (from_intrpt) {
    194 		clean_up(byebye_string);
    195 	}
    196 	check_message();
    197 	killed_by(NULL, QUIT);
    198 }
    199 
    200 /*
    201  * The score file on disk is up to ten entries of the form
    202  *      score block [80 bytes]
    203  *      nickname block [30 bytes]
    204  *
    205  * The score block is to be parsed as follows:
    206  *      bytes 0-1	Rank (" 1" to "10")
    207  *      bytes 2-4	space padding
    208  *      bytes 5-15	Score/gold
    209  *      byte 15 up to a ':'	Login name
    210  *      past the ':'    Death mechanism
    211  *
    212  * The nickname block is an alternate name to be printed in place of the
    213  * login name. Both blocks are supposed to contain a null-terminator.
    214  */
    215 
    216 struct score_entry {
    217 	long gold;
    218 	char username[80];
    219 	char death[80];
    220 	char nickname[30];
    221 };
    222 
    223 #define NUM_SCORE_ENTRIES 10
    224 
    225 static void make_score(struct score_entry *, const object *, int);
    226 
    227 static
    228 void
    229 pad_spaces(char *str, size_t len)
    230 {
    231 	size_t x;
    232 	for (x=strlen(str); x<len-1; x++) {
    233 		str[x] = ' ';
    234 	}
    235 	str[len-1] = 0;
    236 }
    237 
    238 static
    239 void
    240 unpad_spaces(char *str)
    241 {
    242 	size_t x;
    243 	for (x=strlen(str); x>0 && str[x-1]==' '; x--);
    244 	str[x] = 0;
    245 }
    246 
    247 static
    248 int
    249 read_score_entry(struct score_entry *se, FILE *fp)
    250 {
    251 	char score_block[80];
    252 	char nickname_block[30];
    253 	size_t n, x;
    254 
    255 	n = fread(score_block, 1, sizeof(score_block), fp);
    256 	if (n==0) {
    257 		/* EOF */
    258 		return 0;
    259 	}
    260 	if (n != sizeof(score_block)) {
    261 		sf_error();
    262 	}
    263 
    264 	n = fread(nickname_block, 1, sizeof(nickname_block), fp);
    265 	if (n != sizeof(nickname_block)) {
    266 		sf_error();
    267 	}
    268 
    269 	xxxx(score_block, sizeof(score_block));
    270 	xxxx(nickname_block, sizeof(nickname_block));
    271 
    272 	/* Ensure null termination */
    273 	score_block[sizeof(score_block)-1] = 0;
    274 	nickname_block[sizeof(nickname_block)-1] = 0;
    275 
    276 	/* If there are other nulls in the score block, file is corrupt */
    277 	if (strlen(score_block)!=sizeof(score_block)-1) {
    278 		sf_error();
    279 	}
    280 	/* but this is NOT true of the nickname block */
    281 
    282 	/* quash trailing spaces */
    283 	unpad_spaces(score_block);
    284 	unpad_spaces(nickname_block);
    285 
    286 	for (x=5; score_block[x] == ' '; x++);
    287 	se->gold = lget_number(score_block+x);
    288 
    289 	for (x=15; score_block[x] != 0 && score_block[x] != ':'; x++);
    290 	if (score_block[x] == 0) {
    291 		sf_error();
    292 	}
    293 	score_block[x++] = 0;
    294 	strlcpy(se->username, score_block+15, sizeof(se->username));
    295 
    296 	strlcpy(se->death, score_block+x, sizeof(se->death));
    297 	strlcpy(se->nickname, nickname_block, sizeof(se->nickname));
    298 
    299 	return 1;
    300 }
    301 
    302 static
    303 void
    304 write_score_entry(const struct score_entry *se, int rank, FILE *fp)
    305 {
    306 	char score_block[80];
    307 	char nickname_block[30];
    308 
    309 	/* avoid writing crap to score file */
    310 	memset(score_block, 0, sizeof(score_block));
    311 	memset(nickname_block, 0, sizeof(nickname_block));
    312 
    313 	snprintf(score_block, sizeof(score_block),
    314 		 "%2d    %6ld   %s: %s",
    315 		 rank+1, se->gold, se->username, se->death);
    316 	strlcpy(nickname_block, se->nickname, sizeof(nickname_block));
    317 
    318 	/* pad blocks out with spaces */
    319 	pad_spaces(score_block, sizeof(score_block));
    320 	/*pad_spaces(nickname_block, sizeof(nickname_block)); -- wrong! */
    321 
    322 	xxxx(score_block, sizeof(score_block));
    323 	xxxx(nickname_block, sizeof(nickname_block));
    324 
    325 	fwrite(score_block, 1, sizeof(score_block), fp);
    326 	fwrite(nickname_block, 1, sizeof(nickname_block), fp);
    327 }
    328 
    329 void
    330 put_scores(const object *monster, short other)
    331 {
    332 	short i, rank=-1, found_player = -1, numscores = 0;
    333 	struct score_entry scores[NUM_SCORE_ENTRIES];
    334 	const char *name;
    335 	FILE *fp;
    336 	boolean dopause = score_only;
    337 
    338 	md_lock(1);
    339 
    340 	setegid(egid);
    341 	if ((fp = fopen(_PATH_SCOREFILE, "r+")) == NULL &&
    342 	    (fp = fopen(_PATH_SCOREFILE, "w+")) == NULL) {
    343 		setegid(gid);
    344 		messagef(0, "cannot read/write/create score file");
    345 		sf_error();
    346 	}
    347 	setegid(gid);
    348 	rewind(fp);
    349 	(void)xxx(1);
    350 
    351 	for (numscores = 0; numscores < NUM_SCORE_ENTRIES; numscores++) {
    352 		if (read_score_entry(&scores[numscores], fp) == 0) {
    353 			break;
    354 		}
    355 	}
    356 
    357 	/* Search the score list. */
    358 	for (i=0; i<numscores; i++) {
    359 		if (!strcmp(scores[i].username, login_name)) {
    360 			/* found our score */
    361 			if (rogue.gold < scores[i].gold) {
    362 				/* we didn't do as well as last time */
    363 				score_only = 1;
    364 			} else {
    365 				/* we did better; mark entry for removal */
    366 				found_player = i;
    367 			}
    368 			break;
    369 		}
    370 	}
    371 
    372 	/* Remove a superseded entry, if any. */
    373 	if (found_player != -1) {
    374 		numscores--;
    375 		for (i = found_player; i < numscores; i++) {
    376 			scores[i] = scores[i+1];
    377 		}
    378 	}
    379 
    380 	/* If we're going to insert ourselves, do it now */
    381 	if (!score_only) {
    382 
    383 		/* if we aren't better than anyone, add at end. */
    384 		rank = numscores;
    385 
    386 		/* Otherwise, find our slot. */
    387 		for (i = 0; i < numscores; i++) {
    388 			if (rogue.gold >= scores[i].gold) {
    389 				rank = i;
    390 				break;
    391 			}
    392 		}
    393 
    394 		if (rank < NUM_SCORE_ENTRIES) {
    395 			/* Open up a slot */
    396 			for (i = numscores; i > rank; i--) {
    397 				scores[i] = scores[i-1];
    398 			}
    399 			numscores++;
    400 
    401 			/* Put our info in the slot */
    402 			make_score(&scores[rank], monster, other);
    403 		}
    404 
    405 		/* Now rewrite the score file */
    406 
    407 		md_ignore_signals();
    408 		rewind(fp);
    409 		(void)xxx(1);
    410 
    411 		for (i = 0; i < numscores; i++) {
    412 			write_score_entry(&scores[i], i, fp);
    413 		}
    414 	}
    415 	md_lock(0);
    416 	fclose(fp);
    417 
    418 	/* Display the scores */
    419 
    420 	clear();
    421 	mvaddstr(3, 30, "Top  Ten  Rogueists");
    422 	mvaddstr(8, 0, "Rank   Score   Name");
    423 
    424 	for (i = 0; i < numscores; i++) {
    425 		if (i == rank) {
    426 			standout();
    427 		}
    428 
    429 		if (scores[i].nickname[0]) {
    430 			name = scores[i].nickname;
    431 		} else {
    432 			name = scores[i].username;
    433 		}
    434 
    435 		mvprintw(i+10, 0, "%2d    %6ld   %s: %s",
    436 			 i+1, scores[i].gold, name, scores[i].death);
    437 
    438 		if (i == rank) {
    439 			standend();
    440 		}
    441 	}
    442 	refresh();
    443 	messagef(0, "%s", "");		/* gcc objects to just "" */
    444 	if (dopause) {
    445 		messagef(0, "%s", "");
    446 	}
    447 	clean_up("");
    448 }
    449 
    450 static
    451 void
    452 make_score(struct score_entry *se, const object *monster, int other)
    453 {
    454 	const char *death = "bolts from the blue (?)";
    455 	const char *hasamulet;
    456 	char deathbuf[80];
    457 
    458 	se->gold = rogue.gold;
    459 	strlcpy(se->username, login_name, sizeof(se->username));
    460 
    461 	if (other) {
    462 		switch(other) {
    463 		case HYPOTHERMIA:
    464 			death = "died of hypothermia";
    465 			break;
    466 		case STARVATION:
    467 			death = "died of starvation";
    468 			break;
    469 		case POISON_DART:
    470 			death = "killed by a dart";
    471 			break;
    472 		case QUIT:
    473 			death = "quit";
    474 			break;
    475 		case WIN:
    476 			death = "a total winner";
    477 			break;
    478 		case KFIRE:
    479 			death = "killed by fire";
    480 			break;
    481 		}
    482 	} else {
    483 		const char *mn, *article;
    484 
    485 		mn = m_names[monster->m_char - 'A'];
    486 		if (is_vowel(mn[0])) {
    487 			article = "an";
    488 		} else {
    489 			article = "a";
    490 		}
    491 
    492 		snprintf(deathbuf, sizeof(deathbuf),
    493 			 "killed by %s %s", article, mn);
    494 		death = deathbuf;
    495 	}
    496 
    497 	if (other != WIN && has_amulet()) {
    498 		hasamulet = " with amulet";
    499 	} else {
    500 		hasamulet = "";
    501 	}
    502 
    503 	snprintf(se->death, sizeof(se->death), "%s on level %d%s",
    504 		 death, max_level, hasamulet);
    505 
    506 	strlcpy(se->nickname, nick_name, sizeof(se->nickname));
    507 }
    508 
    509 boolean
    510 is_vowel(short ch)
    511 {
    512 	return( (ch == 'a') ||
    513 		(ch == 'e') ||
    514 		(ch == 'i') ||
    515 		(ch == 'o') ||
    516 		(ch == 'u') );
    517 }
    518 
    519 void
    520 sell_pack(void)
    521 {
    522 	object *obj;
    523 	short row = 2, val;
    524 	char buf[DCOLS];
    525 
    526 	obj = rogue.pack.next_object;
    527 
    528 	clear();
    529 	mvaddstr(1, 0, "Value      Item");
    530 
    531 	while (obj) {
    532 		if (obj->what_is != FOOD) {
    533 			obj->identified = 1;
    534 			val = get_value(obj);
    535 			rogue.gold += val;
    536 
    537 			if (row < DROWS) {
    538 				get_desc(obj, buf, sizeof(buf));
    539 				mvprintw(row++, 0, "%5d      %s", val, buf);
    540 			}
    541 		}
    542 		obj = obj->next_object;
    543 	}
    544 	refresh();
    545 	if (rogue.gold > MAX_GOLD) {
    546 		rogue.gold = MAX_GOLD;
    547 	}
    548 	messagef(0, "%s", "");		/* gcc objects to just "" */
    549 }
    550 
    551 int
    552 get_value(const object *obj)
    553 {
    554 	short wc;
    555 	int val;
    556 
    557 	val = 0;
    558 	wc = obj->which_kind;
    559 
    560 	switch(obj->what_is) {
    561 	case WEAPON:
    562 		val = id_weapons[wc].value;
    563 		if ((wc == ARROW) || (wc == DAGGER) || (wc == SHURIKEN) ||
    564 			(wc == DART)) {
    565 			val *= obj->quantity;
    566 		}
    567 		val += (obj->d_enchant * 85);
    568 		val += (obj->hit_enchant * 85);
    569 		break;
    570 	case ARMOR:
    571 		val = id_armors[wc].value;
    572 		val += (obj->d_enchant * 75);
    573 		if (obj->is_protected) {
    574 			val += 200;
    575 		}
    576 		break;
    577 	case WAND:
    578 		val = id_wands[wc].value * (obj->class + 1);
    579 		break;
    580 	case SCROL:
    581 		val = id_scrolls[wc].value * obj->quantity;
    582 		break;
    583 	case POTION:
    584 		val = id_potions[wc].value * obj->quantity;
    585 		break;
    586 	case AMULET:
    587 		val = 5000;
    588 		break;
    589 	case RING:
    590 		val = id_rings[wc].value * (obj->class + 1);
    591 		break;
    592 	}
    593 	if (val <= 0) {
    594 		val = 10;
    595 	}
    596 	return(val);
    597 }
    598 
    599 void
    600 id_all(void)
    601 {
    602 	short i;
    603 
    604 	for (i = 0; i < SCROLS; i++) {
    605 		id_scrolls[i].id_status = IDENTIFIED;
    606 	}
    607 	for (i = 0; i < WEAPONS; i++) {
    608 		id_weapons[i].id_status = IDENTIFIED;
    609 	}
    610 	for (i = 0; i < ARMORS; i++) {
    611 		id_armors[i].id_status = IDENTIFIED;
    612 	}
    613 	for (i = 0; i < WANDS; i++) {
    614 		id_wands[i].id_status = IDENTIFIED;
    615 	}
    616 	for (i = 0; i < POTIONS; i++) {
    617 		id_potions[i].id_status = IDENTIFIED;
    618 	}
    619 }
    620 
    621 void
    622 xxxx(char *buf, short n)
    623 {
    624 	short i;
    625 	unsigned char c;
    626 
    627 	for (i = 0; i < n; i++) {
    628 
    629 		/* It does not matter if accuracy is lost during this assignment */
    630 		c = (unsigned char)xxx(0);
    631 
    632 		buf[i] ^= c;
    633 	}
    634 }
    635 
    636 long
    637 xxx(boolean st)
    638 {
    639 	static long f, s;
    640 	long r;
    641 
    642 	if (st) {
    643 		f = 37;
    644 		s = 7;
    645 		return(0L);
    646 	}
    647 	r = ((f * s) + 9337) % 8887;
    648 	f = s;
    649 	s = r;
    650 	return(r);
    651 }
    652 
    653 void
    654 center(short row, const char *buf)
    655 {
    656 	short margin;
    657 
    658 	margin = ((DCOLS - strlen(buf)) / 2);
    659 	mvaddstr(row, margin, buf);
    660 }
    661 
    662 void
    663 sf_error(void)
    664 {
    665 	md_lock(0);
    666 	messagef(1, "%s", "");		/* gcc objects to just "" */
    667 	clean_up("sorry, score file is out of order");
    668 }
    669